diff --git a/.lgtm.yml b/.lgtm.yml index 929214c96ca7..4077a58b25e4 100755 --- a/.lgtm.yml +++ b/.lgtm.yml @@ -10,6 +10,8 @@ path_classifiers: - javascript/extractor/tests - javascript/ql/src - javascript/ql/test + - python/ql/src + - python/ql/test queries: - include: "*" diff --git a/change-notes/1.19/analysis-cpp.md b/change-notes/1.19/analysis-cpp.md index 6d274150621d..edbab04f34eb 100644 --- a/change-notes/1.19/analysis-cpp.md +++ b/change-notes/1.19/analysis-cpp.md @@ -6,8 +6,8 @@ | **Query** | **Tags** | **Purpose** | |-----------------------------|-----------|--------------------------------------------------------------------| -| Cast between `HRESULT` and a Boolean type (`cpp/hresult-boolean-conversion`) | external/cwe/cwe-253 | Finds logic errors caused by mistakenly treating the Windows `HRESULT` type as a Boolean instead of testing it with the appropriate macros. Enabled by default. | -| Setting a DACL to `NULL` in a `SECURITY_DESCRIPTOR` (`cpp/unsafe-dacl-security-descriptor`) | external/cwe/cwe-732 | This query finds code that creates world-writable objects on Windows by setting their DACL to `NULL`. Enabled by default. | +| Cast between `HRESULT` and a Boolean type (`cpp/hresult-boolean-conversion`) | security, external/cwe/cwe-253 | Finds logic errors caused by mistakenly treating the Windows `HRESULT` type as a Boolean instead of testing it with the appropriate macros. Enabled by default. | +| Setting a DACL to `NULL` in a `SECURITY_DESCRIPTOR` (`cpp/unsafe-dacl-security-descriptor`) | security, external/cwe/cwe-732 | This query finds code that creates world-writable objects on Windows by setting their DACL to `NULL`. Enabled by default. | | Cast from `char*` to `wchar_t*` | security, external/cwe/cwe-704 | Detects potentially dangerous casts from `char*` to `wchar_t*`. Enabled by default on LGTM. | | Dead code due to `goto` or `break` statement (`cpp/dead-code-goto`) | maintainability, external/cwe/cwe-561 | Detects dead code following a `goto` or `break` statement. Enabled by default on LGTM. | | Inconsistent direction of for loop | correctness, external/cwe/cwe-835 | This query detects `for` loops where the increment and guard condition don't appear to correspond. Enabled by default on LGTM. | diff --git a/change-notes/1.19/analysis-csharp.md b/change-notes/1.19/analysis-csharp.md index 260a4ec3fc62..24fe57573a85 100644 --- a/change-notes/1.19/analysis-csharp.md +++ b/change-notes/1.19/analysis-csharp.md @@ -11,7 +11,7 @@ | **Query** | **Tags** | **Purpose** | |-----------------------------|-----------|--------------------------------------------------------------------| | Using a package with a known vulnerability (cs/use-of-vulnerable-package) | security, external/cwe/cwe-937 | Finds project build files that import packages with known vulnerabilities. This is included by default. | - +| Uncontrolled format string (cs/uncontrolled-format-string) | security, external/cwe/cwe-134 | Finds data flow from remote inputs to the format string in `String.Format`. This is included by default. | ## Changes to existing queries diff --git a/change-notes/1.19/analysis-javascript.md b/change-notes/1.19/analysis-javascript.md index b45cb8ad8f7b..3d7ec740283a 100644 --- a/change-notes/1.19/analysis-javascript.md +++ b/change-notes/1.19/analysis-javascript.md @@ -28,6 +28,8 @@ | Stored cross-site scripting (`js/stored-xss`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights uncontrolled stored values flowing into HTML content, indicating a violation of [CWE-079](https://cwe.mitre.org/data/definitions/79.html). Results shown on LGTM by default. | | Unclear precedence of nested operators (`js/unclear-operator-precedence`) | maintainability, correctness, external/cwe/cwe-783 | Highlights nested binary operators whose relative precedence is easy to misunderstand. Results shown on LGTM by default. | | Unneeded defensive code | correctness, external/cwe/cwe-570, external/cwe/cwe-571 | Highlights locations where defensive code is not needed. Results are shown on LGTM by default. | +| Unsafe dynamic method access (`js/unsafe-dynamic-method-access` ) | security, external/cwe/cwe-094 | Highlights code that invokes a user-controlled method on an object with unsafe methods. Results are shown on LGTM by default. | +| Unvalidated dynamic method access (`js/unvalidated-dynamic-method-call` ) | security, external/cwe/cwe-754 | Highlights code that invokes a user-controlled method without guarding against exceptional circumstances. Results are shown on LGTM by default. | | Useless assignment to property | maintainability | Highlights property assignments whose value is always overwritten. Results are shown on LGTM by default. | | User-controlled data in file | security, external/cwe/cwe-912 | Highlights locations where user-controlled data is written to a file. Results are not shown on LGTM by default. | @@ -43,11 +45,11 @@ | Duplicate 'if' condition | Lower severity | The severity of this rule has been revised to "warning". | | Duplicate switch case | Lower severity | The severity of this rule has been revised to "warning". | | Information exposure through a stack trace | More results | This rule now also flags cases where the entire exception object (including the stack trace) may be exposed. | -| Missing CSRF middleware | Fewer false-positive results | This rule now recognizes additional CSRF protection middlewares. | | Missing 'this' qualifier | Fewer false-positive results | This rule now recognizes additional intentional calls to global functions. | +| Missing CSRF middleware | Fewer false-positive results | This rule now recognizes additional CSRF protection middlewares. | | Missing variable declaration | Lower severity | The severity of this rule has been revised to "warning". | | Regular expression injection | Fewer false-positive results | This rule now identifies calls to `String.prototype.search` with more precision. | -| Remote property injection | Fewer results | The precision of this rule has been revised to "medium". Results are no longer shown on LGTM by default. | +| Remote property injection | Fewer results | The precision of this rule has been revised to "medium". Furthermore, it no longer flags dynamic method calls, which are now handled by two new queries. Results are no longer shown on LGTM by default. | | Self assignment | Fewer false-positive results | This rule now ignores self-assignments preceded by a JSDoc comment with a `@type` tag. | | Server-side URL redirect | Fewer false-positive results | This rule now recognizes safe redirects in more cases. | | Server-side URL redirect | More results | This rule now recognizes redirection calls in more cases. | @@ -58,6 +60,7 @@ | Unused variable, import, function or class | Fewer false-positive results | This rule now flags fewer variables that may be used by `eval` calls. | | Unused variable, import, function or class | Fewer results | This rule now flags import statements with multiple unused imports once. | | Useless assignment to local variable | Fewer false-positive results | This rule now recognizes additional ways default values can be set. | +| Useless conditional | More results, fewer false-positive results | This rule now recognizes conditionals in more cases, but no longer flags certain defensive coding patterns. | | Whitespace contradicts operator precedence | Fewer false-positive results | This rule no longer flags operators with asymmetric whitespace. | | Wrong use of 'this' for static method | More results, fewer false-positive results | This rule now recognizes inherited methods. | diff --git a/change-notes/1.19/analysis-python.md b/change-notes/1.19/analysis-python.md new file mode 100644 index 000000000000..177a10a0ce2e --- /dev/null +++ b/change-notes/1.19/analysis-python.md @@ -0,0 +1,97 @@ +# Improvements to Python analysis + + +## General improvements + +> Changes that affect alerts in many files or from many queries +> For example, changes to file classification + +### Representation of the control flow graph + +The representation of the control flow graph (CFG) has been modified to better reflect the semantics of Python. + +The following statement types no longer have a CFG node for the statement itself, as their sub-expressions already contain all the +semantically significant information: + +* `ExprStmt` +* `If` +* `Assign` +* `Import` + +For example, the CFG for `if cond: foo else bar` now starts with the CFG node for `cond`. + +For the following statement types, the CFG node for the statement now follows the CFG nodes of its sub-expressions to better reflect the semantics: + +* `Print` +* `TemplateWrite` +* `ImportStar` + +For example the CFG for `print foo` (in Python 2) has changed from `print -> foo` to `foo -> print`, better reflecting the runtime behavior. + + +The CFG for the `with` statement has been re-ordered to more closely reflect the semantics. +For the `with` statement: +```python +with cm as var: + body +``` +The order of the CFG changes from: + + + cm + var + body + +to: + + cm + + var + body + +A new predicate `Stmt.getAnEntryNode()` has been added to make it easier to write reachability queries involving statements. + + +## New queries + +| **Query** | **Tags** | **Purpose** | +|-----------------------------|-----------|--------------------------------------------------------------------| +| Flask app is run in debug mode (`py/flask-debug`) | security, external/cwe/cwe-215, external/cwe/cwe-489 | Finds instances where a Flask application is run in debug mode. Enabled on LGTM by default. | +| Information exposure through an exception (`py/stack-trace-exposure`) | security, external/cwe/cwe-209, external/cwe/cwe-497 | Finds instances where information about an exception may be leaked to an external user. Enabled on LGTM by default. | +| Request without certificate validation (`py/request-without-cert-validation`) | security, external/cwe/cwe-295 | Finds requests where certificate verification has been explicitly turned off, possibly allowing man-in-the-middle attacks. Not enabled on LGTM by default. | + +## Changes to existing queries + +All taint-tracking queries now support visualization of paths in QL for Eclipse. +Most security alerts are now visible on LGTM by default. + +| **Query** | **Expected impact** | **Change** | +|----------------------------|------------------------|------------------------------------------------------------------| +| Assert statement tests the truth value of a literal constant (`py/assert-literal-constant`) | reliability, correctness | Checks whether an assert statement is testing the truth of a literal constant value. Not shown by default. | +| Code injection (`py/code-injection`) | Supports path visualization and is now visible on LGTM by default | No change to expected results | +| Command injection (`py/command-line-injection`) | Additional sinks in the `os`, and `popen` modules | Possibility of new results | +| Deserializing untrusted input (`py/unsafe-deserialization`) | Supports path visualization | No change to expected results | +| Encoding error (`py/encoding-error`) | Better alert location | Alert is now shown at the position of the first offending character, rather than at the top of the file. | +| Missing call to \_\_init\_\_ during object initialization (`py/missing-call-to-init`) | Fewer false positive results | Results where it is likely that the full call chain has not been analyzed are no longer reported. | +| Reflected server-side cross-site scripting (`py/reflective-xss`) | Supports path visualization and is now visible on LGTM by default | No change to expected results | +| SQL query built from user-controlled sources (`py/sql-injection`) | Supports path visualization and is now visible on LGTM by default | No change to expected results | +| Uncontrolled data used in path expression (`py/path-injection`) | Supports path visualization and is now visible on LGTM by default | No change to expected results | +| Uncontrolled command line (`py/command-line-injection`) | Supports path visualization and is now visible on LGTM by default | No change to expected results | +| URL redirection from remote source (`py/url-redirection`) | Fewer false positive results and now supports path visualization | Taint is no longer tracked from the right hand side of binary expressions. In other words `SAFE + TAINTED` is now treated as safe. | + + +## Changes to code extraction + +* Improved scalability: Scaling is near linear to at least 20 CPU cores. +* Five levels of logging can be selected: `ERROR`, `WARN`, `INFO`, `DEBUG` and `TRACE`. `WARN` is the stand-alone default, but `INFO` will be used when run by LGTM. +* The `-v` flag can be specified multiple times to increase logging level by one per `-v`. +* The `-q` flag has been added and can be specified multiple times to reduce the logging level by one per `-q`. +* Log lines are now in the `[SEVERITY] message` style and never overlap. +* Extractor now outputs the location of the first offending character when an EncodingError is encountered. + +## Changes to QL libraries + +* Taint tracking analysis now understands HTTP requests in the `twisted` library. + +* The analysis now handles `isinstance` and `issubclass` tests involving the basic abstract base classes better. For example, the test `issubclass(list, collections.Sequence)` is now understood to be `True` +* Taint tracking automatically tracks tainted mappings and collections, without you having to add additional taint kinds. This means that custom taints are tracked from `x` to `y` in the following flow: `l = [x]; y =l[0]`. diff --git a/change-notes/1.19/extractor-javascript.md b/change-notes/1.19/extractor-javascript.md index 0ad335af0dc7..8aa278e623c5 100644 --- a/change-notes/1.19/extractor-javascript.md +++ b/change-notes/1.19/extractor-javascript.md @@ -32,3 +32,8 @@ extraction: * The TypeScript compiler is now bundled with the distribution, and no longer needs to be installed manually. Should the compiler version need to be overridden, set the `SEMMLE_TYPESCRIPT_HOME` environment variable to point to an installation of the `typescript` NPM package. + +* The extractor now supports [Optional Chaining](https://github.com/tc39/proposal-optional-chaining) expressions. + +* The extractor now supports additional [Flow](https://flow.org/) syntax. + diff --git a/cpp/ql/src/Critical/NotInitialised.ql b/cpp/ql/src/Critical/NotInitialised.ql index 719cec8c301e..4a336935c450 100644 --- a/cpp/ql/src/Critical/NotInitialised.ql +++ b/cpp/ql/src/Critical/NotInitialised.ql @@ -9,9 +9,7 @@ */ import cpp -// This query is the JSF version -// -// (see also InitialisationNotRun.ql and GlobalUseBeforeInit.ql) +// See also InitialisationNotRun.ql and GlobalUseBeforeInit.ql // Holds if s defines variable v (conservative) predicate defines(ControlFlowNode s, Variable lv) { diff --git a/cpp/ql/src/META-INF/MANIFEST.MF b/cpp/ql/src/META-INF/MANIFEST.MF deleted file mode 100644 index b7387557cc25..000000000000 --- a/cpp/ql/src/META-INF/MANIFEST.MF +++ /dev/null @@ -1,8 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: Semmle C/C++ Default Queries -Bundle-SymbolicName: com.semmle.plugin.semmlecode.cpp.queries;singleton:=true -Bundle-Version: 1.18.3.qualifier -Bundle-Vendor: Semmle Ltd. -Bundle-ActivationPolicy: lazy -Require-Bundle: com.semmle.plugin.qdt.ui;bundle-version="[1.18.3.qualifier,1.18.3.qualifier]" diff --git a/cpp/ql/src/Security/CWE/CWE-497/ExposedSystemData.ql b/cpp/ql/src/Security/CWE/CWE-497/ExposedSystemData.ql index ef804061e975..fc214b54f8ec 100644 --- a/cpp/ql/src/Security/CWE/CWE-497/ExposedSystemData.ql +++ b/cpp/ql/src/Security/CWE/CWE-497/ExposedSystemData.ql @@ -33,14 +33,7 @@ abstract class SystemData extends Element { result = getAnExpr() or // flow via global or member variable (conservative approximation) - exists(Variable var | - ( - var.getAnAssignedValue() = getAnExprIndirect() or - var.getAnAccess() = getAnExprIndirect() - ) and - result = var.getAnAccess() and - not var instanceof LocalScopeVariable - ) or + result = getAnAffectedVar().getAnAccess() or // flow via stack variable definitionUsePair(_, getAnExprIndirect(), result) or @@ -50,6 +43,17 @@ abstract class SystemData extends Element { // flow from assigned value to assignment expression result.(AssignExpr).getRValue() = getAnExprIndirect() } + + /** Gets a global or member variable that may be affected by this system + * data (conservative approximation). + */ + private Variable getAnAffectedVar() { + ( + result.getAnAssignedValue() = this.getAnExprIndirect() or + result.getAnAccess() = this.getAnExprIndirect() + ) and + not result instanceof LocalScopeVariable + } } /** diff --git a/cpp/ql/src/jsf/4.05 Libraries/AV Rule 24.qhelp b/cpp/ql/src/jsf/4.05 Libraries/AV Rule 24.qhelp index 1a6404486ec9..7513d84d5267 100644 --- a/cpp/ql/src/jsf/4.05 Libraries/AV Rule 24.qhelp +++ b/cpp/ql/src/jsf/4.05 Libraries/AV Rule 24.qhelp @@ -5,8 +5,12 @@ + + + +

-This rule finds calls to the standard library functions abort, exit, getenv and system. +This query highlights calls to the standard library functions abort, exit, getenv and system. The functions abort and exit should not be called as they immediately terminate the program and will bypass all the normal error and exception handling routines in the software. This is especially important in software which is run on systems without an interactive OS, as restarting the software may require a complete reboot diff --git a/cpp/ql/src/jsf/4.10 Classes/AV Rule 85.qhelp b/cpp/ql/src/jsf/4.10 Classes/AV Rule 85.qhelp index a92a3beac088..61275fedefab 100644 --- a/cpp/ql/src/jsf/4.10 Classes/AV Rule 85.qhelp +++ b/cpp/ql/src/jsf/4.10 Classes/AV Rule 85.qhelp @@ -5,8 +5,12 @@ + + + +

-This rule ensures that all operators with opposites (e.g. == and !=) are both defined, and +This query ensures that all operators with opposites (e.g. == and !=) are both defined, and that one of them is defined in terms of the other. This just enforces the consistency of meaning of the operators.

diff --git a/cpp/ql/src/jsf/4.10 Classes/AV Rule 85.ql b/cpp/ql/src/jsf/4.10 Classes/AV Rule 85.ql index fcab5ac5cd88..4f81c6518c19 100644 --- a/cpp/ql/src/jsf/4.10 Classes/AV Rule 85.ql +++ b/cpp/ql/src/jsf/4.10 Classes/AV Rule 85.ql @@ -21,23 +21,50 @@ predicate oppositeOperators(string op1, string op2) { /* this match is very syntactic: we simply check that op1 is defined as !op2(_, _) */ predicate implementedAsNegationOf(Operator op1, Operator op2) { - exists(Block b, ReturnStmt r, NotExpr n, FunctionCall c | + exists(Block b, ReturnStmt r, NotExpr n, Expr o | b = op1.getBlock() and b.getNumStmt() = 1 and r = b.getStmt(0) and n = r.getExpr() and - c = n.getOperand() and - c.getTarget() = op2) + o = n.getOperand() and + ( + o instanceof LTExpr and op2.hasName("operator<") or + o instanceof LEExpr and op2.hasName("operator<=") or + o instanceof GTExpr and op2.hasName("operator>") or + o instanceof GEExpr and op2.hasName("operator>=") or + o instanceof EQExpr and op2.hasName("operator==") or + o instanceof NEExpr and op2.hasName("operator!=") or + o.(FunctionCall).getTarget() = op2 + ) + ) +} + +predicate classIsCheckableFor(Class c, string op) { + oppositeOperators(op, _) and + // We check the template, not its instantiations + not c instanceof ClassTemplateInstantiation and + // Member functions of templates are not necessarily instantiated, so + // if the function we want to check exists, then make sure that its + // body also exists + ((c instanceof TemplateClass) + implies + forall(Function f | f = c.getAMember() and f.hasName(op) + | exists(f.getEntryPoint()))) } from Class c, string op, string opp, Operator rator where c.fromSource() and oppositeOperators(op, opp) and + classIsCheckableFor(c, op) and + classIsCheckableFor(c, opp) and rator = c.getAMember() and rator.hasName(op) and - not exists(Operator oprator | oprator = c.getAMember() and - oprator.hasName(opp) and - ( implementedAsNegationOf(rator, oprator) - or implementedAsNegationOf(oprator, rator))) + forex(Operator aRator | + aRator = c.getAMember() and aRator.hasName(op) | + not exists(Operator oprator | + oprator = c.getAMember() and + oprator.hasName(opp) and + ( implementedAsNegationOf(aRator, oprator) + or implementedAsNegationOf(oprator, aRator)))) select c, "When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator " + op + " is declared on line " + rator.getLocation().getStartLine().toString() + ", but it is not defined in terms of its opposite operator " + opp + "." diff --git a/cpp/ql/src/jsf/4.13 Functions/AV Rule 111.qhelp b/cpp/ql/src/jsf/4.13 Functions/AV Rule 111.qhelp index 577784582cc7..7dbbb12dba31 100644 --- a/cpp/ql/src/jsf/4.13 Functions/AV Rule 111.qhelp +++ b/cpp/ql/src/jsf/4.13 Functions/AV Rule 111.qhelp @@ -5,8 +5,12 @@ + + + +

-This rule finds return statements that return pointers to an object allocated on the stack. The lifetime +This query highlights return statements that return pointers to an object allocated on the stack. The lifetime of a stack allocated memory location only lasts until the function returns, , and the contents of that memory become undefined after that. Clearly, using a pointer to stack memory after the function has already returned will have undefined results. diff --git a/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 135.qhelp b/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 135.qhelp index 33de5f3b5812..6729583ff772 100644 --- a/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 135.qhelp +++ b/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 135.qhelp @@ -5,8 +5,12 @@ + + + +

-This rule finds identifiers in an inner scope that hide (have the same name as) an identifier in an outer scope. +This query highlights identifiers in an inner scope that hide (have the same name as) an identifier in an outer scope. This should be avoided as it can cause confusion about the actual variable being used in an expression.

diff --git a/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 140.qhelp b/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 140.qhelp index 50354e8788cd..e184b9edd6f7 100644 --- a/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 140.qhelp +++ b/cpp/ql/src/jsf/4.15 Declarations and Definitions/AV Rule 140.qhelp @@ -5,8 +5,12 @@ + + + +

-This rule finds variables with the register storage class specifier. Modern compilers are now capable of +This query highlights variables with the register storage class specifier. Modern compilers are now capable of optimal register placement, and overriding it could lead to worse performance.

diff --git a/cpp/ql/src/jsf/4.17 Types/AV Rule 147.qhelp b/cpp/ql/src/jsf/4.17 Types/AV Rule 147.qhelp index 489ab0fe575e..75c4f03e9800 100644 --- a/cpp/ql/src/jsf/4.17 Types/AV Rule 147.qhelp +++ b/cpp/ql/src/jsf/4.17 Types/AV Rule 147.qhelp @@ -5,8 +5,12 @@ + + + +

-This rule finds portions of code that can expose the floating point implementation of the underlying +This query highlights portions of code that can expose the floating point implementation of the underlying machine. Manually manipulating the bits in the float is prone to mistakes and is unportable. Floating point implementations can vary across architectures, and bit-field packing can differ across compilers, making manual bit-manipulation of floats inadvisable. diff --git a/cpp/ql/src/jsf/4.18 Constants/AV Rule 151.1.qhelp b/cpp/ql/src/jsf/4.18 Constants/AV Rule 151.1.qhelp index 8e3770c0b451..9c6c369cfed9 100644 --- a/cpp/ql/src/jsf/4.18 Constants/AV Rule 151.1.qhelp +++ b/cpp/ql/src/jsf/4.18 Constants/AV Rule 151.1.qhelp @@ -5,8 +5,12 @@ + + + +

-This rule finds string literals that are assigned to a non-const variable. String literals +This query highlights string literals that are assigned to a non-const variable. String literals should not be changed, since they are usually stored in the data section, and depending on the architecture, writing to the data section will cause undefined behavior, such as memory corruption or memory write error.

diff --git a/cpp/ql/src/jsf/4.20 Unions and Bit Fields/AV Rule 154.qhelp b/cpp/ql/src/jsf/4.20 Unions and Bit Fields/AV Rule 154.qhelp index 317ac901570b..3c502a249285 100644 --- a/cpp/ql/src/jsf/4.20 Unions and Bit Fields/AV Rule 154.qhelp +++ b/cpp/ql/src/jsf/4.20 Unions and Bit Fields/AV Rule 154.qhelp @@ -5,6 +5,10 @@ + + + +

This query finds bit fields with members that are not explicitly declared to be unsigned. The sign of plain char, short, int, or long bit field is implementation-specific, and declaring diff --git a/cpp/ql/src/jsf/4.21 Operators/AV Rule 165.qhelp b/cpp/ql/src/jsf/4.21 Operators/AV Rule 165.qhelp index 48d385848021..5961991fcdc1 100644 --- a/cpp/ql/src/jsf/4.21 Operators/AV Rule 165.qhelp +++ b/cpp/ql/src/jsf/4.21 Operators/AV Rule 165.qhelp @@ -5,8 +5,12 @@ + + + +

-This rule finds unsigned values that are being negated. Behavior is undefined in such cases. +This query finds unsigned values that are being negated. Behavior is undefined in such cases. Negating integer values produces the two's complement of that number, which cannot represent negative values of large unsigned values (values where the sign bit is used) and are most likely to be interpreted as a smaller positive integer instead. diff --git a/cpp/ql/src/jsf/4.24 Control Flow Structures/AV Rule 189.qhelp b/cpp/ql/src/jsf/4.24 Control Flow Structures/AV Rule 189.qhelp index 211a694ae16d..e65f5b8dfb42 100644 --- a/cpp/ql/src/jsf/4.24 Control Flow Structures/AV Rule 189.qhelp +++ b/cpp/ql/src/jsf/4.24 Control Flow Structures/AV Rule 189.qhelp @@ -4,6 +4,10 @@ + + + +

Use of goto statements makes code more difficult to understand and maintain. Consequently, the use of goto statements is deprecated except as a mechanism for breaking out of multiple nested loops. This rule identifies any goto statements that are called directly or from a single nested loop as violations.

diff --git a/cpp/ql/src/plugin.xml b/cpp/ql/src/plugin.xml deleted file mode 100644 index d7036c7c3479..000000000000 --- a/cpp/ql/src/plugin.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/cpp/ql/src/semmle/code/cpp/Class.qll b/cpp/ql/src/semmle/code/cpp/Class.qll index 8d4414f03f38..99cd54e62fac 100644 --- a/cpp/ql/src/semmle/code/cpp/Class.qll +++ b/cpp/ql/src/semmle/code/cpp/Class.qll @@ -418,6 +418,12 @@ class Class extends UserType { */ predicate isPOD() { is_pod_class(underlyingElement(this)) } + /** + * Holds if this class is a standard-layout class [N4140 9(7)]. Also holds + * for structs in C programs. + */ + predicate isStandardLayout() { is_standard_layout_class(underlyingElement(this)) } + /** * Holds if this class is abstract, in other words whether it declares one * or more pure virtual member functions. diff --git a/cpp/ql/src/semmle/code/cpp/exprs/BuiltInOperations.qll b/cpp/ql/src/semmle/code/cpp/exprs/BuiltInOperations.qll index 26c01adea8c6..cd25fd45f79c 100644 --- a/cpp/ql/src/semmle/code/cpp/exprs/BuiltInOperations.qll +++ b/cpp/ql/src/semmle/code/cpp/exprs/BuiltInOperations.qll @@ -202,6 +202,27 @@ class BuiltInOperationBuiltInShuffleVector extends BuiltInOperation, @builtinshu override string toString() { result = "__builtin_shufflevector" } } +/** + * A clang `__builtin_convertvector` expression. + */ +class BuiltInOperationBuiltInConvertVector extends BuiltInOperation, @builtinconvertvector { + override string toString() { result = "__builtin_convertvector" } +} + +/** + * A clang `__builtin_addressof` expression (can be used to implement C++'s std::addressof). + */ +class BuiltInOperationBuiltInAddressOf extends UnaryOperation, BuiltInOperation, @builtinaddressof { + /** Gets the function or variable whose address is taken. */ + Declaration getAddressable() { + result = this.getOperand().(Access).getTarget() + // this handles the case where we are taking the address of a reference variable + or result = this.getOperand().(ReferenceDereferenceExpr).getChild(0).(Access).getTarget() + } + + override string getOperator() { result = "__builtin_addressof" } +} + /** * The `__is_trivially_constructible` type trait. */ @@ -369,3 +390,10 @@ class BuiltInOperationIsFinal extends BuiltInOperation, @isfinalexpr { class BuiltInChooseExpr extends BuiltInOperation, @builtinchooseexpr { override string toString() { result = "__builtin_choose_expr" } } + +/** + * Fill operation on a GNU vector. + */ +class VectorFillOperation extends UnaryOperation, @vec_fill { + override string getOperator() { result = "(vector fill)" } +} diff --git a/cpp/ql/src/semmle/code/cpp/exprs/Cast.qll b/cpp/ql/src/semmle/code/cpp/exprs/Cast.qll index 9825f0794236..c9876a0a794d 100644 --- a/cpp/ql/src/semmle/code/cpp/exprs/Cast.qll +++ b/cpp/ql/src/semmle/code/cpp/exprs/Cast.qll @@ -497,7 +497,12 @@ class DynamicCast extends Cast, @dynamic_cast { * specified by the `__declspec(uuid)` attribute. */ class UuidofOperator extends Expr, @uuidof { - override string toString() { result = "__uuidof(" + getTypeOperand().getName() + ")" } + override string toString() { + if exists(getTypeOperand()) then + result = "__uuidof(" + getTypeOperand().getName() + ")" + else + result = "__uuidof(0)" + } override int getPrecedence() { result = 15 } diff --git a/cpp/ql/src/semmlecode.cpp.dbscheme b/cpp/ql/src/semmlecode.cpp.dbscheme index 692743332eaa..732e8b4112b9 100644 --- a/cpp/ql/src/semmlecode.cpp.dbscheme +++ b/cpp/ql/src/semmlecode.cpp.dbscheme @@ -691,6 +691,7 @@ usertype_uuid( ); is_pod_class(unique int id: @usertype ref); +is_standard_layout_class(unique int id: @usertype ref); is_complete(unique int id: @usertype ref); @@ -1429,6 +1430,9 @@ case @expr.kind of | 319 = @noexceptexpr | 320 = @builtinshufflevector | 321 = @builtinchooseexpr +| 322 = @builtinaddressof +| 323 = @vec_fill +| 324 = @builtinconvertvector ; new_allocated_type( diff --git a/cpp/ql/src/semmlecode.cpp.dbscheme.stats b/cpp/ql/src/semmlecode.cpp.dbscheme.stats index c4a3a4d1d68f..58497e488216 100644 --- a/cpp/ql/src/semmlecode.cpp.dbscheme.stats +++ b/cpp/ql/src/semmlecode.cpp.dbscheme.stats @@ -1,7 +1,7 @@ @compilation -9060 +9061 @externalDataElement @@ -9,11 +9,11 @@ @duplication -202892 +202913 @similarity -239090 +239114 @external_package @@ -25,55 +25,55 @@ @location_default -8022713 +8023536 @location_stmt -5663436 +3131917 @location_expr -22100594 +11250844 @diagnostic -63199 +63205 @file -55998 +56003 @folder -7763 +7764 @macroinvocation -37324960 +37328050 @function -3598577 +3598946 @fun_decl -2988576 +2988883 @var_decl -4558796 +4559414 @type_decl -1355244 +1355383 @namespace_decl -127544 +127557 @using -197383 +197403 @static_assert @@ -81,23 +81,23 @@ @parameter -4460380 +4460837 @membervariable -344801 +344836 @globalvariable -336971 +337006 @localvariable -668696 +668764 @enumconstant -94952 +94956 @builtintype @@ -105,27 +105,27 @@ @derivedtype -3507081 +3507440 @decltype -63437 +63443 @usertype -3306162 +3306501 @type_mention -7732581 +7732644 @routinetype -508632 +508685 @ptrtomember -13331 +13333 @specifier @@ -133,7 +133,7 @@ @gnuattribute -426272 +426316 @stdattribute @@ -141,7 +141,7 @@ @declspec -34302 +34304 @msattribute @@ -161,7 +161,7 @@ @attribute_arg_constant -135907 +135921 @attribute_arg_type @@ -169,19 +169,19 @@ @derivation -212282 +212304 @frienddecl -88104 +88173 @comment -1452211 +1452360 @namespace -7676 +7677 @specialnamequalifyingelement @@ -189,47 +189,51 @@ @namequalifier -938842 +938938 @value -10236794 +10237844 @initialiser -1574731 +1575751 -@varaccess -6733144 +@c_style_cast +5272497 + + +@literal +5228505 @errorexpr -52430 +52435 @address_of -516925 +516978 @reference_to -1331402 +1331539 @indirect -341674 +341709 @ref_indirect -1492336 +1492489 @array_to_pointer -1280990 +1281121 @vacuous_destructor_call -4962 +4963 @assume @@ -237,11 +241,11 @@ @parexpr -3660444 +3660819 @arithnegexpr -737838 +737913 @unaryplusexpr @@ -249,11 +253,11 @@ @complementexpr -34593 +34597 @notexpr -329480 +329514 @conjugation @@ -269,39 +273,39 @@ @postincrexpr -63538 +63545 @postdecrexpr -8002 +8003 @preincrexpr -74974 +74981 @predecrexpr -30567 +30570 @conditionalexpr -206817 +206838 @addexpr -235591 +235615 @subexpr -176294 +176312 @mulexpr -98275 +98285 @divexpr -57252 +57258 @remexpr @@ -333,59 +337,59 @@ @paddexpr -121302 +121315 @psubexpr -31835 +31838 @pdiffexpr -28782 +28785 @lshiftexpr -310180 +310212 @rshiftexpr -71982 +71989 @andexpr -248607 +248633 @orexpr -138236 +138250 @xorexpr -16130 +16132 @eqexpr -293602 +293632 @neexpr -115974 +115986 @gtexpr -68308 +68315 @ltexpr -76855 +76863 @geexpr -30753 +30756 @leexpr -273093 +273121 @minexpr @@ -397,19 +401,19 @@ @assignexpr -712754 +712827 @assignaddexpr -77608 +77616 @assignsubexpr -9568 +9569 @assignmulexpr -6984 +6985 @assigndivexpr @@ -425,15 +429,15 @@ @assignrshiftexpr -4540 +4541 @assignandexpr -10844 +10845 @assignorexpr -26580 +26583 @assignxorexpr @@ -449,19 +453,19 @@ @andlogicalexpr -132294 +132307 @orlogicalexpr -72721 +72728 @commaexpr -21632 +21634 @subscriptexpr -254218 +254244 @virtfunptrexpr @@ -469,7 +473,7 @@ @callexpr -224652 +224675 @vastartexpr @@ -488,12 +492,16 @@ 34 +@varaccess +6733835 + + @thisaccess -1381324 +1381465 @new_expr -35638 +35641 @delete_expr @@ -501,11 +509,11 @@ @throw_expr -23560 +23562 @condition_decl -12283 +12284 @braced_init_list @@ -517,7 +525,7 @@ @runtime_sizeof -273449 +273477 @runtime_alignof @@ -529,19 +537,19 @@ @expr_stmt -200825 +200846 @routineexpr -2799216 +2799503 @type_operand -50822 +50828 @offsetofexpr -43479 +43483 @hasassignexpr @@ -621,7 +629,7 @@ @typescompexpr -3671 +3672 @intaddrexpr @@ -632,16 +640,12 @@ 97 -@literal -5227969 - - @uuidof 121 @aggregateliteral -1097142 +1097255 @delete_array_expr @@ -653,15 +657,15 @@ @ctordirectinit -118873 +118885 @ctorvirtualinit -4735 +4736 @ctorfieldinit -285602 +285631 @ctordelegatinginit @@ -669,7 +673,7 @@ @dtordirectdestruct -29820 +29824 @dtorvirtualdestruct @@ -677,35 +681,31 @@ @dtorfielddestruct -32307 +32311 @static_cast -246223 +246248 @reinterpret_cast -30482 +30483 @const_cast -9861 +9862 @dynamic_cast 1146 -@c_style_cast -5271956 - - @lambdaexpr 2399 @param_ref -66962 +66968 @noopexpr @@ -820,70 +820,82 @@ 3088 -@lambdacapture -259 +@builtinaddressof +1 -@stmt_return -1378123 +@vec_fill +1 -@stmt_block -1623114 +@builtinconvertvector +1 -@stmt_decl -690316 +@lambdacapture +259 @stmt_expr -1581675 +1581837 @stmt_if -661372 +661440 @stmt_while -40817 +40821 @stmt_goto -127245 +127258 @stmt_label -119526 +119538 + + +@stmt_return +1378265 + + +@stmt_block +1623280 @stmt_end_test_while -220160 +220163 @stmt_for -42107 +42109 @stmt_switch_case -390492 +390532 @stmt_switch -76506 +76514 @stmt_asm -299048 +299079 @stmt_try_block -19300 +19302 @stmt_microsoft_try 268 +@stmt_decl +690386 + + @stmt_set_vla_size 105 @@ -897,15 +909,15 @@ @stmt_empty -89215 +89224 @stmt_continue -12152 +12154 @stmt_break -323188 +323221 @stmt_range_based_for @@ -913,47 +925,47 @@ @stmt_handler -21300 - - -@ppd_plain_include -274562 +21302 @ppd_if -130410 +130423 @ppd_ifdef -48656 +48661 @ppd_ifndef -74822 +74830 @ppd_elif -16326 +16328 @ppd_else -49759 +49764 @ppd_endif -253900 +253926 + + +@ppd_plain_include +274591 @ppd_define -305508 +305539 @ppd_undef -16662 +16663 @ppd_line -13973 +13974 @ppd_error @@ -1006,11 +1018,11 @@ compilations -9060 +9061 id -9060 +9061 cwd @@ -1028,7 +1040,7 @@ 1 2 -9060 +9061 @@ -1059,11 +1071,11 @@ compilation_args -538292 +538347 id -6898 +6899 num @@ -1071,7 +1083,7 @@ arg -28985 +28988 @@ -1309,7 +1321,7 @@ 1 2 -27667 +27670 2 @@ -1330,7 +1342,7 @@ 1 2 -28489 +28492 2 @@ -1345,11 +1357,11 @@ compilation_compiling_files -10077 +10078 id -9060 +9061 num @@ -1357,7 +1369,7 @@ file -5092 +5093 @@ -1371,7 +1383,7 @@ 1 2 -9039 +9040 47 @@ -1392,7 +1404,7 @@ 1 2 -9039 +9040 47 @@ -1470,7 +1482,7 @@ 2 3 -4811 +4812 3 @@ -1491,7 +1503,7 @@ 1 2 -4692 +4693 2 @@ -1506,11 +1518,11 @@ compilation_time -40136 +40140 id -9017 +9018 num @@ -1522,7 +1534,7 @@ seconds -12910 +12543 @@ -1557,7 +1569,7 @@ 4 5 -9017 +9018 @@ -1571,18 +1583,23 @@ 12 +2 +3 +10 + + 3 4 -1784 +1611 4 5 -7211 +7374 -53 -57 +54 +59 21 @@ -1641,26 +1658,26 @@ 3 4 -21 +32 4 5 -75 +21 5 6 -346 +367 6 7 -75 +97 -1183 -1184 +1149 +1150 10 @@ -1707,23 +1724,23 @@ 12 -17 -18 +16 +17 10 -18 -19 +17 +18 10 -525 -526 +500 +501 10 -765 -766 +769 +770 10 @@ -1740,22 +1757,22 @@ 1 2 -9298 +8802 2 3 -2032 +2195 3 5 -1102 +908 5 -554 -475 +592 +638 @@ -1771,12 +1788,12 @@ 1 2 -12164 +11700 2 50 -746 +843 @@ -1792,17 +1809,12 @@ 1 2 -11515 +11008 2 3 -1373 - - -3 -4 -21 +1535 @@ -1812,11 +1824,11 @@ diagnostic_for -742605 +742681 diagnostic -63199 +63205 compilation @@ -1847,7 +1859,7 @@ 2 3 -57328 +57333 252 @@ -1868,7 +1880,7 @@ 1 2 -63199 +63205 @@ -1884,7 +1896,7 @@ 1 2 -63199 +63205 @@ -1900,7 +1912,7 @@ 2 3 -637 +638 93 @@ -1962,7 +1974,7 @@ 2 3 -637 +638 93 @@ -2061,7 +2073,7 @@ 2 3 -1156 +1157 3 @@ -2152,7 +2164,7 @@ 254 255 -983 +984 313 @@ -2183,19 +2195,19 @@ compilation_finished -9060 +9061 id -9060 +9061 cpu_seconds -8325 +8283 elapsed_seconds -291 +259 @@ -2209,7 +2221,7 @@ 1 2 -9060 +9061 @@ -2225,7 +2237,7 @@ 1 2 -9060 +9061 @@ -2241,17 +2253,17 @@ 1 2 -7676 +7591 2 -4 -637 +3 +627 -5 -6 -10 +3 +5 +64 @@ -2267,12 +2279,12 @@ 1 2 -8044 +8056 2 3 -281 +227 @@ -2288,51 +2300,51 @@ 1 2 -97 +54 2 3 -10 +43 3 4 -21 +10 4 -7 +5 21 -8 -9 -32 +6 +7 +21 -9 -25 +14 +18 21 -27 -32 +20 +26 21 -50 -112 +70 +88 21 -115 -123 +107 +114 21 -141 -158 +170 +180 21 @@ -2349,51 +2361,51 @@ 1 2 -97 +54 2 3 -10 +43 3 4 -21 +10 4 -7 +5 21 -8 -9 -32 +6 +7 +21 -9 -25 +14 +18 21 -27 -32 +20 +26 21 -49 -96 +68 +84 21 -107 -119 +89 +107 21 -137 -149 +161 +169 21 @@ -2642,11 +2654,11 @@ duplicateCode -202892 +202913 id -202892 +202913 relativePath @@ -2654,7 +2666,7 @@ equivClass -81806 +81814 @@ -2668,7 +2680,7 @@ 1 2 -202892 +202913 @@ -2684,7 +2696,7 @@ 1 2 -202892 +202913 @@ -2812,17 +2824,17 @@ 1 2 -20579 +20581 2 3 -34908 +34911 3 4 -12017 +12019 4 @@ -2832,7 +2844,7 @@ 5 9 -6745 +6746 9 @@ -2853,7 +2865,7 @@ 1 2 -80801 +80809 2 @@ -2868,19 +2880,19 @@ similarCode -239090 +239114 id -239090 +239114 relativePath -2653 +2654 equivClass -63903 +63910 @@ -2894,7 +2906,7 @@ 1 2 -239090 +239114 @@ -2910,7 +2922,7 @@ 1 2 -239090 +239114 @@ -3093,22 +3105,22 @@ 2 3 -26487 +26489 3 4 -12396 +12397 4 5 -7530 +7531 5 6 -5135 +5136 6 @@ -3118,7 +3130,7 @@ 7 9 -5181 +5182 9 @@ -3139,12 +3151,12 @@ 1 2 -27187 +27190 2 3 -17612 +17613 3 @@ -3154,12 +3166,12 @@ 4 5 -4256 +4257 5 8 -5745 +5746 8 @@ -3174,19 +3186,19 @@ tokens -44698936 +44703518 id -314482 +314514 offset -23246 +23248 beginLine -846814 +846901 beginColumn @@ -3194,7 +3206,7 @@ endLine -846814 +846901 endColumn @@ -3212,72 +3224,72 @@ 100 101 -8554 +8555 101 102 -30879 +30882 102 105 -25278 +25281 105 108 -29028 +29031 108 112 -29028 +29031 112 115 -14929 +14931 115 117 -24335 +24338 117 124 -24727 +24729 124 132 -25816 +25819 132 150 -24161 +24163 150 184 -24349 +24352 184 200 -24007 +24010 200 338 -23602 +23605 339 3330 -5781 +5782 @@ -3298,47 +3310,47 @@ 5 6 -116400 +116412 6 7 -19042 +19044 7 8 -31109 +31112 8 12 -27645 +27648 12 17 -28847 +28849 17 19 -20970 +20972 19 22 -28414 +28416 22 27 -23679 +23681 27 525 -16905 +16907 @@ -3354,12 +3366,12 @@ 3 25 -23672 +23675 25 30 -24678 +24680 30 @@ -3369,27 +3381,27 @@ 32 33 -173165 +173183 33 50 -23693 +23695 50 60 -25502 +25504 60 72 -23763 +23765 72 120 -12674 +12675 @@ -3410,47 +3422,47 @@ 5 6 -116400 +116412 6 7 -19042 +19044 7 8 -31109 +31112 8 12 -27645 +27648 12 17 -28847 +28849 17 19 -20970 +20972 19 22 -28414 +28416 22 27 -23679 +23681 27 525 -16905 +16907 @@ -3466,12 +3478,12 @@ 3 25 -26919 +26922 25 31 -28483 +28486 31 @@ -3481,27 +3493,27 @@ 32 33 -174073 +174091 33 54 -24992 +24994 54 64 -25481 +25483 64 78 -23882 +23884 78 126 -8840 +8841 @@ -3593,7 +3605,7 @@ 4 5 -2925 +2926 6 @@ -3613,7 +3625,7 @@ 13 16 -1696 +1697 17 @@ -3694,7 +3706,7 @@ 12 15 -1284 +1285 15 @@ -3719,7 +3731,7 @@ 143 178 -851 +852 @@ -3740,7 +3752,7 @@ 4 5 -2925 +2926 6 @@ -3760,7 +3772,7 @@ 13 16 -1696 +1697 17 @@ -3882,42 +3894,42 @@ 1 2 -415478 +415520 2 3 -110730 +110741 3 4 -49195 +49200 4 5 -43260 +43264 5 7 -72288 +72296 7 10 -70926 +70934 10 23 -64174 +64180 23 136 -20760 +20762 @@ -3933,52 +3945,52 @@ 1 7 -72491 +72498 7 12 -70277 +70284 12 23 -63622 +63629 23 32 -40117 +40121 32 33 -269986 +270013 33 41 -67658 +67665 41 54 -64327 +64334 54 67 -64062 +64069 67 88 -63657 +63664 88 153 -63615 +63622 153 @@ -3999,47 +4011,47 @@ 1 5 -72358 +72365 5 9 -64202 +64208 9 14 -64921 +64928 14 26 -66744 +66750 26 32 -18665 +18667 32 33 -353426 +353462 33 37 -71918 +71925 37 42 -69453 +69460 42 85 -63559 +63566 85 @@ -4060,7 +4072,7 @@ 1 2 -846787 +846873 2 @@ -4081,47 +4093,47 @@ 1 5 -72288 +72296 5 9 -64146 +64152 9 14 -64893 +64900 14 26 -66688 +66695 26 32 -18679 +18681 32 33 -353768 +353805 33 37 -74376 +74384 37 42 -65640 +65647 42 82 -63657 +63664 82 @@ -4522,42 +4534,42 @@ 1 2 -415464 +415506 2 3 -110730 +110741 3 4 -49202 +49207 4 5 -43260 +43264 5 7 -72288 +72296 7 10 -70926 +70934 10 23 -64181 +64187 23 136 -20760 +20762 @@ -4573,52 +4585,52 @@ 1 7 -72491 +72498 7 12 -70277 +70284 12 23 -63615 +63622 23 32 -40124 +40128 32 33 -269986 +270013 33 41 -67658 +67665 41 54 -64327 +64334 54 67 -64062 +64069 67 88 -63657 +63664 88 153 -63615 +63622 153 @@ -4639,7 +4651,7 @@ 1 2 -846787 +846873 2 @@ -4660,47 +4672,47 @@ 1 5 -72358 +72365 5 9 -64202 +64208 9 14 -64928 +64935 14 26 -66737 +66743 26 32 -18665 +18667 32 33 -353426 +353462 33 37 -71918 +71925 37 42 -69453 +69460 42 85 -63559 +63566 85 @@ -4721,47 +4733,47 @@ 1 5 -72295 +72303 5 9 -64139 +64145 9 14 -64893 +64900 14 26 -66688 +66695 26 32 -18679 +18681 32 33 -353768 +353805 33 37 -74376 +74384 37 42 -65640 +65647 42 82 -63657 +63664 82 @@ -6648,31 +6660,31 @@ locations_default -8022713 +8023536 id -8022713 +8023536 container -63761 +63768 startLine -130172 +130185 startColumn -5914 +5915 endLine -130064 +130077 endColumn -7914 +7915 @@ -6686,7 +6698,7 @@ 1 2 -8022713 +8023536 @@ -6702,7 +6714,7 @@ 1 2 -8022713 +8023536 @@ -6718,7 +6730,7 @@ 1 2 -8022713 +8023536 @@ -6734,7 +6746,7 @@ 1 2 -8022713 +8023536 @@ -6750,7 +6762,7 @@ 1 2 -8022713 +8023536 @@ -6766,7 +6778,7 @@ 1 2 -8401 +8402 2 @@ -6776,12 +6788,12 @@ 19 25 -5384 +5385 25 31 -5135 +5136 31 @@ -6806,22 +6818,22 @@ 90 122 -4800 +4801 122 181 -4789 +4790 181 315 -4811 +4812 315 1243 -4789 +4790 1268 @@ -6842,32 +6854,32 @@ 1 2 -8401 +8402 2 15 -5373 +5374 15 20 -5838 +5839 20 25 -5254 +5255 25 32 -5654 +5655 32 41 -5157 +5158 41 @@ -6897,7 +6909,7 @@ 260 8116 -3816 +3817 @@ -6913,32 +6925,32 @@ 1 2 -8401 +8402 2 4 -5849 +5850 4 8 -5849 +5850 8 11 -4735 +4736 11 14 -5427 +5428 14 18 -5860 +5861 18 @@ -6958,12 +6970,12 @@ 36 47 -4789 +4790 47 68 -4865 +4866 68 @@ -6984,12 +6996,12 @@ 1 2 -8401 +8402 2 15 -5384 +5385 15 @@ -7004,12 +7016,12 @@ 25 32 -5557 +5558 32 41 -5200 +5201 41 @@ -7029,12 +7041,12 @@ 92 141 -4854 +4855 141 268 -4800 +4801 268 @@ -7055,7 +7067,7 @@ 1 2 -8401 +8402 2 @@ -7090,7 +7102,7 @@ 38 45 -5449 +5450 45 @@ -7100,12 +7112,12 @@ 54 65 -5081 +5082 65 80 -4973 +4974 80 @@ -7126,22 +7138,22 @@ 1 2 -20403 +20405 2 3 -15461 +15463 3 4 -13904 +13906 4 5 -9947 +9948 5 @@ -7151,37 +7163,37 @@ 6 7 -5892 +5893 7 8 -6595 +6596 8 10 -10066 +10067 10 14 -10315 +10316 14 31 -9785 +9786 31 117 -9774 +9775 117 1729 -9763 +9764 1734 @@ -7202,42 +7214,42 @@ 1 2 -37433 +37436 2 3 -33043 +33046 3 4 -9147 +9148 4 5 -8347 +8348 5 7 -11180 +11181 7 17 -10109 +10110 17 62 -9796 +9797 62 820 -9763 +9764 821 @@ -7258,27 +7270,27 @@ 1 2 -21333 +21335 2 3 -15159 +15160 3 4 -16294 +16296 4 5 -9158 +9159 5 6 -7795 +7796 6 @@ -7288,27 +7300,27 @@ 7 8 -7298 +7299 8 10 -9796 +9797 10 14 -10250 +10251 14 29 -9958 +9959 29 63 -9936 +9937 63 @@ -7329,22 +7341,22 @@ 1 2 -95496 +95506 2 3 -15202 +15203 3 6 -10325 +10327 6 177 -9147 +9148 @@ -7360,27 +7372,27 @@ 1 2 -21030 +21032 2 3 -15461 +15463 3 4 -14629 +14630 4 5 -10066 +10067 5 6 -7557 +7558 6 @@ -7395,27 +7407,27 @@ 8 10 -10358 +10359 10 14 -10077 +10078 14 31 -9893 +9894 31 73 -9861 +9862 73 196 -8736 +8737 @@ -7492,7 +7504,7 @@ 1 2 -2832 +2833 2 @@ -7548,7 +7560,7 @@ 2 3 -983 +984 3 @@ -7609,7 +7621,7 @@ 2 3 -983 +984 3 @@ -7716,22 +7728,22 @@ 1 2 -20414 +20416 2 3 -15213 +15214 3 4 -13915 +13917 4 5 -9861 +9862 5 @@ -7746,32 +7758,32 @@ 7 8 -6941 +6942 8 10 -9623 +9624 10 14 -10369 +10370 14 31 -9817 +9818 31 118 -9839 +9840 118 1791 -9763 +9764 1806 @@ -7792,42 +7804,42 @@ 1 2 -37357 +37361 2 3 -32989 +32992 3 4 -9082 +9083 4 5 -8239 +8240 5 7 -11363 +11365 7 17 -10152 +10154 17 63 -9871 +9872 63 897 -9763 +9764 898 @@ -7848,22 +7860,22 @@ 1 2 -94869 +94878 2 3 -15051 +15052 3 6 -10109 +10110 6 37 -9763 +9764 37 @@ -7884,27 +7896,27 @@ 1 2 -21322 +21324 2 3 -14953 +14955 3 4 -16391 +16393 4 5 -9060 +9061 5 6 -7774 +7775 6 @@ -7914,32 +7926,32 @@ 7 8 -7190 +7191 8 10 -9785 +9786 10 14 -10325 +10327 14 29 -9925 +9926 29 63 -9925 +9926 63 164 -7471 +7472 @@ -7955,62 +7967,62 @@ 1 2 -21062 +21064 2 3 -15202 +15203 3 4 -14661 +14663 4 5 -9915 +9916 5 6 -7795 +7796 6 7 -5773 +5774 7 8 -7006 +7007 8 10 -9936 +9937 10 14 -10196 +10197 14 31 -9861 +9862 31 73 -9893 +9894 73 196 -8758 +8759 @@ -8087,7 +8099,7 @@ 1 2 -3470 +3471 2 @@ -8102,7 +8114,7 @@ 4 9 -637 +638 9 @@ -8305,31 +8317,31 @@ locations_stmt -5663436 +3131917 id -5663436 +3131917 container -9452 +15270 startLine -46583 +93168 startColumn -301 +225 endLine -44962 +92231 endColumn -415 +289 @@ -8343,7 +8355,7 @@ 1 2 -5663436 +3131917 @@ -8359,7 +8371,7 @@ 1 2 -5663436 +3131917 @@ -8375,7 +8387,7 @@ 1 2 -5663436 +3131917 @@ -8391,7 +8403,7 @@ 1 2 -5663436 +3131917 @@ -8407,7 +8419,7 @@ 1 2 -5663436 +3131917 @@ -8422,73 +8434,68 @@ 1 -6 -713 - - -6 -14 -763 +2 +1255 -14 -27 -741 +2 +4 +1336 -27 -48 -733 +4 +7 +1211 -48 -76 -733 +7 +13 +1151 -76 -115 -733 +13 +22 +1167 -115 -171 -713 +22 +37 +1205 -171 -237 -716 +37 +57 +1146 -237 -348 -711 +57 +90 +1146 -348 -528 -716 +90 +138 +1163 -528 -840 -713 +138 +213 +1148 -841 -1620 -711 +213 +340 +1148 -1620 -11419 -711 +340 +658 +1146 -11860 -84476 -38 +658 +60302 +1048 @@ -8503,68 +8510,73 @@ 1 -5 -824 +2 +1574 -5 -11 -727 +2 +3 +683 -11 -20 -749 +3 +5 +1171 -20 -33 -744 +5 +9 +1202 -33 -52 -719 +9 +17 +1256 -52 -73 -724 +17 +28 +1189 -73 -105 -722 +28 +44 +1194 -105 -153 -713 +44 +70 +1158 -153 -210 -716 +70 +108 +1153 -210 -307 -713 +108 +164 +1157 -307 -490 -711 +164 +262 +1154 -490 -903 -711 +262 +479 +1146 -903 -10013 -672 +479 +2642 +1146 + + +2671 +49152 +87 @@ -8580,72 +8592,67 @@ 1 2 -127 +1526 2 3 -1358 +2202 3 4 -835 +727 4 5 -522 +793 5 7 -760 +1274 7 -9 -608 - - -9 -12 -755 +10 +1388 -12 -16 -802 +10 +14 +1245 -16 -21 -747 +14 +19 +1256 -21 -27 -730 +19 +25 +1209 -27 -35 -733 +25 +34 +1219 -35 -46 -727 +34 +47 +1149 -46 -71 -719 +47 +80 +1149 -71 -76 -22 +80 +150 +133 @@ -8660,68 +8667,73 @@ 1 -5 -874 +2 +1620 -5 -11 -783 +2 +3 +738 -11 -19 -747 +3 +4 +770 -19 -31 -744 +4 +7 +1231 -31 -49 -716 +7 +13 +1180 -49 -69 -719 +13 +21 +1149 -69 -99 -724 +21 +34 +1147 -99 -142 -724 +34 +54 +1167 -142 -198 -719 +54 +86 +1177 -198 -292 -713 +86 +131 +1149 -292 -469 -711 +131 +208 +1157 -469 -894 -711 +208 +353 +1148 + + +353 +887 +1146 -895 -8668 -561 +887 +49173 +491 @@ -8736,68 +8748,68 @@ 1 -3 -597 +2 +1687 -3 -6 -813 +2 +3 +706 -6 -10 -760 +3 +4 +1073 -10 -15 -722 +4 +7 +1303 -15 -22 -755 +7 +11 +1159 -22 -29 -713 +11 +17 +1321 -29 -38 -744 +17 +24 +1188 -38 -46 -763 +24 +33 +1217 -46 -53 -766 +33 +43 +1194 -53 -60 -777 +43 +54 +1170 -60 +54 67 -821 +1204 67 -73 -733 +81 +1170 -73 -109 -481 +81 +191 +878 @@ -8813,57 +8825,32 @@ 1 2 -8699 +44649 2 3 -7462 +19855 3 4 -3741 +8301 4 -6 -3682 - - -6 -10 -3608 - - -10 -19 -3635 - - -19 -37 -3503 - - -37 -81 -3514 - - -81 -228 -3494 +8 +7162 -228 -953 -3494 +8 +40 +7047 -953 -5507 -1746 +40 +4340 +6154 @@ -8879,52 +8866,32 @@ 1 2 -9878 +55827 2 3 -8669 +16019 3 -4 -3943 - - -4 -6 -3409 - - -6 -11 -4009 - - -11 -22 -3572 - - -22 -42 -3583 +5 +7157 -42 -98 -3503 +5 +26 +7009 -98 -299 -3500 +26 +2511 +6988 -299 -946 -2515 +2515 +3629 +168 @@ -8940,47 +8907,32 @@ 1 2 -11403 +47574 2 3 -9524 +20620 3 4 -5155 +7857 4 -5 -3207 - - -5 -7 -4001 - - -7 -10 -3716 - - -10 -17 -3843 +9 +7749 -17 -35 -3558 +9 +40 +7039 -35 -65 -2174 +40 +111 +2329 @@ -8996,42 +8948,27 @@ 1 2 -15952 +63133 2 3 -9313 +14324 3 -4 -3962 - - -4 -6 -4169 - - -6 -9 -3647 - - -9 -15 -3649 +7 +7942 -15 -27 -3569 +7 +45 +7005 -27 -62 -2318 +45 +78 +764 @@ -9047,52 +8984,32 @@ 1 2 -9598 +53701 2 3 -8323 +16943 3 -4 -4236 - - -4 -6 -3848 - - -6 -10 -3680 - - -10 -18 -3705 - - -18 -29 -3577 +5 +8087 -29 -46 -3511 +5 +20 +7052 -46 -64 -3497 +20 +112 +7015 -64 -84 -2603 +112 +128 +370 @@ -9108,67 +9025,67 @@ 1 2 -19 +35 2 -3 -13 +6 +18 -3 -7 -24 +6 +19 +17 -7 -20 -24 +19 +57 +17 -22 -177 -24 +60 +114 +18 -217 -428 -24 +114 +183 +17 -563 -874 -24 +186 +840 +17 -964 -1575 -24 +913 +3429 +17 -1900 -2624 -24 +3544 +5947 +17 -2880 -4018 -24 +5959 +9037 +17 -4047 -5227 -24 +9414 +13488 +17 -5723 -27015 -24 +13932 +657187 +17 -32001 -631213 -19 +805542 +805543 +1 @@ -9184,62 +9101,67 @@ 1 2 -22 +36 2 -4 -27 +6 +17 -4 -12 -24 +6 +15 +17 -14 -74 -24 +15 +44 +17 -120 -246 -24 +47 +65 +17 -280 -418 -24 +65 +100 +17 -444 -562 -24 +116 +471 +17 -577 -732 -24 +488 +1386 +17 -732 -778 -27 +1474 +2213 +17 -791 -836 -24 +2257 +2850 +17 -840 -974 -24 +2859 +3486 +17 -978 -3377 -24 +3503 +8396 +17 + + +8639 +11768 +2 @@ -9255,67 +9177,67 @@ 1 2 -19 +35 2 -3 -13 +6 +18 -3 -7 -24 +6 +18 +17 -7 -19 -24 +19 +56 +17 -20 -169 -24 +57 +110 +17 -193 -375 -24 +110 +169 +17 -446 -688 -24 +174 +662 +17 -780 -1061 -24 +662 +1973 +17 -1091 -1251 -24 +2017 +2605 +17 -1279 -1359 -24 +2670 +3135 +17 -1364 -1475 -24 +3192 +4817 +17 -1568 -2722 -24 +4962 +25767 +17 -3168 -13523 -19 +37222 +49428 +2 @@ -9331,67 +9253,67 @@ 1 2 -19 +35 2 -3 -13 +6 +18 -3 -7 -24 +6 +18 +17 -7 -19 -24 +19 +56 +17 -20 -173 -24 +57 +110 +18 -192 -384 -24 +110 +173 +17 -453 -681 -24 +178 +669 +17 -794 -1045 -24 +727 +1997 +17 -1088 -1247 -24 +2115 +2671 +17 -1258 -1359 -24 +2691 +3196 +17 -1366 -1484 -24 +3245 +4970 +17 -1530 -2730 -24 +5087 +37196 +17 -2911 -13557 -19 +49251 +49252 +1 @@ -9407,67 +9329,62 @@ 1 2 -22 +41 2 -3 -24 - - -3 -5 -27 +4 +20 -5 -10 -19 +4 +7 +20 -10 -12 -24 +7 +11 +18 -12 +11 15 -19 +17 -16 -23 -22 +15 +25 +17 -23 -36 -24 +25 +42 +18 -38 -59 -24 +42 +73 +17 -59 -65 -27 +73 +88 +18 -65 -69 -24 +88 +97 +17 -69 -87 -24 +98 +196 +17 -101 -133 -13 +202 +229 +5 @@ -9483,57 +9400,32 @@ 1 2 -7501 +43632 2 3 -6231 +19716 3 4 -4078 +8322 4 -6 -3934 - - -6 -10 -3774 - - -10 -18 -3461 - - -18 -36 -3541 - - -36 -74 -3395 - - -74 -202 -3381 +8 +7349 -202 -744 -3373 +8 +39 +6937 -745 -4895 -2288 +39 +4131 +6275 @@ -9549,52 +9441,32 @@ 1 2 -10625 +56050 2 3 -7839 +15137 3 -4 -3627 - - -4 -6 -3121 - - -6 -11 -3815 - - -11 -22 -3536 - - -22 -42 -3375 +5 +6965 -42 -94 -3384 +5 +25 +7033 -94 -288 -3373 +25 +2556 +6918 -288 -838 -2263 +2567 +3214 +128 @@ -9610,47 +9482,27 @@ 1 2 -13799 +62435 2 3 -9322 +13431 3 -4 -4319 - - -4 -5 -2548 - - -5 -7 -3168 - - -7 -11 -3652 - - -11 -18 -3458 +6 +7541 -18 -33 -3386 +6 +27 +6959 -33 -51 -1306 +27 +82 +1865 @@ -9666,47 +9518,32 @@ 1 2 -9731 +46765 2 3 -9402 +20825 3 4 -5489 +7524 4 -5 -3359 - - -5 -7 -3848 - - -7 -10 -3588 - - -10 -16 -3494 +9 +7829 -16 -31 -3409 +9 +40 +6963 -31 -59 -2637 +40 +111 +2325 @@ -9722,52 +9559,32 @@ 1 2 -10321 +54884 2 3 -7722 +15362 3 -4 -3879 - - -4 -6 -3326 - - -6 -10 -3558 - - -10 -18 -3633 - - -18 -29 -3403 +5 +7686 -29 -46 -3414 +5 +20 +7108 -46 -65 -3492 +20 +114 +6936 -65 -84 -2210 +114 +127 +255 @@ -9783,67 +9600,67 @@ 1 2 -44 +33 2 -4 -33 +3 +22 -4 -10 -33 +3 +5 +22 -10 -39 -33 +5 +15 +22 -41 -108 -33 +15 +52 +22 -121 -2080 -33 +54 +170 +22 -2761 -5372 -33 +182 +354 +22 -6979 -16638 -33 +384 +1540 +22 -16827 -22275 -33 +1585 +5263 +22 -22375 -26352 -33 +6366 +17112 +22 -26611 -28939 -33 +17231 +30606 +22 -29002 -39437 -33 +31003 +41294 +22 -105156 -513228 -5 +42366 +308723 +14 @@ -9859,67 +9676,62 @@ 1 2 -58 +49 2 3 -19 +21 3 7 -35 +23 7 -19 -33 - - -20 -49 -33 +27 +22 -62 -263 -33 +28 +70 +23 -513 -1036 -33 +75 +190 +22 -1086 -1396 -33 +190 +435 +22 -1399 -1737 -33 +487 +1298 +22 -1738 -1889 -33 +1324 +3922 +22 -1893 -1951 -33 +4103 +5173 +22 -1952 -2347 -33 +5198 +5950 +22 -3361 -3362 -2 +5955 +8739 +19 @@ -9935,67 +9747,67 @@ 1 2 -49 +33 2 3 -24 +22 3 -6 -30 +5 +22 -7 -21 -33 +5 +15 +22 -21 -42 -33 +15 +52 +22 -57 -271 -33 +54 +169 +22 -290 -1569 -33 +177 +328 +22 -1579 -2600 -33 +351 +1166 +22 -2675 -3394 -33 +1187 +2811 +22 -3507 -4212 -33 +2935 +4771 +22 -4223 -4454 -33 +4775 +6677 +22 -4506 -5479 -33 +6677 +8457 +22 -5487 -7385 -11 +8608 +23574 +14 @@ -10011,62 +9823,62 @@ 1 2 -55 +56 2 3 -30 +22 3 -4 -27 - - -4 6 -33 +18 6 -13 -33 +9 +22 -14 -22 -33 +9 +12 +23 -22 -31 -33 +12 +21 +22 -32 -37 -35 +21 +38 +22 -37 -44 -35 +38 +49 +22 -44 -49 -33 +50 +60 +22 -49 -53 -35 +60 +71 +22 -53 -100 -27 +71 +79 +23 + + +90 +211 +15 @@ -10082,7 +9894,7 @@ 1 2 -55 +33 2 @@ -10091,58 +9903,58 @@ 3 -7 -33 +5 +22 -7 -21 -35 +5 +15 +22 -22 -59 -33 +15 +52 +22 -77 -372 -33 +54 +169 +22 -764 -1800 -33 +176 +324 +22 -1820 -2849 -33 +351 +1165 +22 -2893 -3480 -33 +1183 +2629 +22 -3651 -4070 -33 +2866 +4735 +22 -4093 -4246 -33 +4759 +6586 +22 -4256 -6581 -33 +6652 +8394 +22 -7012 -7013 -2 +8509 +22154 +14 @@ -10152,11 +9964,11 @@ locations_expr -22100594 +11250844 id -22100594 +11250844 container @@ -10164,7 +9976,7 @@ startLine -229959 +229982 startColumn @@ -10172,7 +9984,7 @@ endLine -230470 +230493 endColumn @@ -10190,7 +10002,7 @@ 1 2 -22100594 +11250844 @@ -10206,7 +10018,7 @@ 1 2 -22100594 +11250844 @@ -10222,7 +10034,7 @@ 1 2 -22100594 +11250844 @@ -10238,7 +10050,7 @@ 1 2 -22100594 +11250844 @@ -10254,7 +10066,7 @@ 1 2 -22100594 +11250844 @@ -10269,73 +10081,73 @@ 1 -18 -308 +4 +342 -18 -35 -311 +4 +12 +316 -35 -91 -310 +12 +52 +314 -91 -232 -311 +52 +133 +308 -232 -390 -311 +133 +226 +308 -391 -591 +226 +344 308 -591 -918 +344 +521 308 -920 -1419 +524 +781 308 -1424 -2132 +781 +1244 308 -2133 -3358 +1245 +1906 308 -3360 -5751 +1906 +3217 308 -5751 -11505 +3218 +6819 308 -11533 -44701 +6828 +36458 308 -44702 -908128 -77 +36650 +477758 +39 @@ -10629,7 +10441,7 @@ 95 106 -338 +339 106 @@ -10649,7 +10461,7 @@ 165 416 -225 +226 @@ -10664,78 +10476,63 @@ 1 +2 +11443 + + +2 3 -11417 +32903 3 4 -10703 +42897 4 5 -23157 +17687 5 -7 -7722 +6 +11861 -7 +6 8 -19339 +18184 8 -9 -2580 - - -9 -10 -25221 - - -10 11 -18585 +20927 11 -13 -18660 - - -13 -19 -19368 - - -19 -27 -18307 +16 +18029 -27 -49 -17652 +16 +24 +17445 -49 -185 -17252 +24 +65 +17382 -185 -1650 -17248 +65 +633 +17251 -1650 -12896 -2740 +633 +3777 +3969 @@ -10751,37 +10548,37 @@ 1 2 -124950 +124963 2 3 -26125 +26128 3 4 -18887 +18889 4 5 -16123 +16125 5 11 -17878 +17880 11 70 -17336 +17337 70 1186 -8656 +8657 @@ -10797,57 +10594,57 @@ 1 2 -11448 +11449 2 3 -32931 +32934 3 4 -42960 +42964 4 5 -18275 +18277 5 6 -13426 +13427 6 7 -16158 +16160 7 9 -16571 +16573 9 12 -17577 +17579 12 17 -17539 +17541 17 30 -17857 +17859 30 95 -17303 +17305 95 @@ -10868,27 +10665,27 @@ 1 2 -116700 +116712 2 3 -75011 +75018 3 4 -15013 +15014 4 8 -19069 +19071 8 55 -4164 +4165 @@ -10904,57 +10701,57 @@ 1 2 -22347 +22349 2 3 -40101 +40105 3 4 -26207 +26209 4 5 -17848 +17850 5 6 -23567 +23570 6 8 -11308 +11309 8 11 -20817 +20819 11 16 -18571 +18573 16 25 -17292 +17294 25 70 -17315 +17316 70 183 -14582 +14583 @@ -10969,73 +10766,73 @@ 1 -3 -39 +2 +42 -3 -6 -40 +2 +4 +36 -6 -17 -39 +4 +9 +43 -18 -63 -39 +9 +27 +38 -67 -285 +28 +83 38 -293 -961 +84 +250 38 -1059 -2449 +253 +778 38 -2583 -5443 +810 +1792 38 -5520 -11130 +1854 +4696 38 -11557 -30612 +4896 +16149 38 -31990 -74443 +16741 +45080 38 -76164 -156094 +45408 +79627 38 -161229 -675130 +80051 +397640 38 -827356 -1129530 -3 +469072 +662955 +2 @@ -11349,73 +11146,63 @@ 1 +2 +11920 + + +2 3 -11925 +45973 3 4 -27719 +9768 4 +5 +35964 + + +5 6 -3719 +11900 6 7 -27350 +14377 7 9 -9530 +16562 9 -10 -25931 - - -10 -12 -11229 - - -12 13 -15855 +17921 13 -17 -18701 - - -17 -24 -18762 - - -24 -40 -17877 +19 +17851 -40 -111 -17300 +19 +32 +17356 -111 -682 -17294 +32 +149 +17305 -682 -12585 -7272 +149 +3776 +13591 @@ -11431,37 +11218,37 @@ 1 2 -125252 +125265 2 3 -25952 +25954 3 4 -18772 +18774 4 5 -16313 +16315 5 11 -18171 +18173 11 73 -17344 +17345 73 1184 -8663 +8664 @@ -11477,27 +11264,27 @@ 1 2 -144940 +144955 2 3 -24518 +24521 3 4 -33768 +33771 4 7 -20411 +20413 7 23 -6830 +6831 @@ -11513,62 +11300,62 @@ 1 2 -11925 +11926 2 3 -46000 +46004 3 4 -9909 +9910 4 5 -36707 +36711 5 6 -12392 +12393 6 7 -16821 +16822 7 9 -17404 +17406 9 12 -18010 +18012 12 17 -17414 +17416 17 29 -17882 +17884 29 92 -17334 +17336 92 196 -8667 +8668 @@ -11584,57 +11371,57 @@ 1 2 -39629 +39633 2 3 -20522 +20524 3 4 -11727 +11728 4 5 -34332 +34335 5 6 -23031 +23034 6 8 -17840 +17842 8 12 -20038 +20040 12 18 -19915 +19917 18 34 -17781 +17783 34 107 -17388 +17390 107 185 -8262 +8263 @@ -11649,68 +11436,68 @@ 1 -3 -55 +2 +40 -3 -6 -49 +2 +3 +48 -6 -11 -44 +3 +9 +51 -11 -19 -45 +9 +15 +49 -19 -76 +15 +37 45 -82 -485 +37 +178 45 -519 -1557 +178 +515 45 -1577 -4435 +518 +1625 45 -4620 -13289 +1701 +5223 45 -13504 -39575 +5589 +18000 45 -44746 -101047 +18544 +48348 45 -104789 -222396 +48446 +103789 45 -222609 -374478 -38 +104548 +165720 +43 @@ -12009,23 +11796,23 @@ numlines -460971 +461018 element_id -460733 +460780 num_lines -8866 +8867 num_code -7049 +7050 num_comment -3708 +3709 @@ -12039,7 +11826,7 @@ 1 2 -460538 +460585 2 @@ -12060,7 +11847,7 @@ 1 2 -460581 +460629 2 @@ -12081,7 +11868,7 @@ 1 2 -460722 +460769 2 @@ -12158,7 +11945,7 @@ 2 3 -1275 +1276 3 @@ -12204,12 +11991,12 @@ 1 2 -3989 +3990 2 3 -1275 +1276 3 @@ -12357,7 +12144,7 @@ 1 2 -2897 +2898 2 @@ -12555,11 +12342,11 @@ diagnostics -63199 +63205 id -63199 +63205 severity @@ -12575,11 +12362,11 @@ full_error_message -59825 +59831 location -15894 +15896 @@ -12593,7 +12380,7 @@ 1 2 -63199 +63205 @@ -12609,7 +12396,7 @@ 1 2 -63199 +63205 @@ -12625,7 +12412,7 @@ 1 2 -63199 +63205 @@ -12641,7 +12428,7 @@ 1 2 -63199 +63205 @@ -12657,7 +12444,7 @@ 1 2 -63199 +63205 @@ -13143,7 +12930,7 @@ 1 2 -59815 +59821 313 @@ -13164,7 +12951,7 @@ 1 2 -59825 +59831 @@ -13180,7 +12967,7 @@ 1 2 -59825 +59831 @@ -13196,7 +12983,7 @@ 1 2 -59825 +59831 @@ -13212,7 +12999,7 @@ 1 2 -59825 +59831 @@ -13238,7 +13025,7 @@ 3 4 -5957 +5958 4 @@ -13269,7 +13056,7 @@ 1 2 -15894 +15896 @@ -13285,7 +13072,7 @@ 1 2 -15894 +15896 @@ -13301,7 +13088,7 @@ 1 2 -15894 +15896 @@ -13327,7 +13114,7 @@ 3 4 -5957 +5958 4 @@ -13352,19 +13139,19 @@ files -55998 +56003 id -55998 +56003 name -55998 +56003 simple -38200 +38204 ext @@ -13386,7 +13173,7 @@ 1 2 -55998 +56003 @@ -13402,7 +13189,7 @@ 1 2 -55998 +56003 @@ -13418,7 +13205,7 @@ 1 2 -55998 +56003 @@ -13434,7 +13221,7 @@ 1 2 -55998 +56003 @@ -13450,7 +13237,7 @@ 1 2 -55998 +56003 @@ -13466,7 +13253,7 @@ 1 2 -55998 +56003 @@ -13482,7 +13269,7 @@ 1 2 -55998 +56003 @@ -13498,7 +13285,7 @@ 1 2 -55998 +56003 @@ -13514,7 +13301,7 @@ 1 2 -28945 +28948 2 @@ -13545,7 +13332,7 @@ 1 2 -28945 +28948 2 @@ -13576,7 +13363,7 @@ 1 2 -33789 +33792 2 @@ -13602,7 +13389,7 @@ 1 2 -38200 +38204 @@ -13860,15 +13647,15 @@ folders -7763 +7764 id -7763 +7764 name -7763 +7764 simple @@ -13886,7 +13673,7 @@ 1 2 -7763 +7764 @@ -13902,7 +13689,7 @@ 1 2 -7763 +7764 @@ -13918,7 +13705,7 @@ 1 2 -7763 +7764 @@ -13934,7 +13721,7 @@ 1 2 -7763 +7764 @@ -13950,7 +13737,7 @@ 1 2 -1913 +1914 2 @@ -13986,7 +13773,7 @@ 1 2 -1913 +1914 2 @@ -14016,15 +13803,15 @@ containerparent -63739 +63746 parent -7763 +7764 child -63739 +63746 @@ -14073,7 +13860,7 @@ 13 20 -637 +638 20 @@ -14099,7 +13886,7 @@ 1 2 -63739 +63746 @@ -14109,7 +13896,7 @@ fileannotations -5310386 +5310931 id @@ -14121,11 +13908,11 @@ name -50299 +50305 value -44093 +44098 @@ -14360,7 +14147,7 @@ 2 3 -5135 +5136 3 @@ -14370,32 +14157,32 @@ 6 8 -4292 +4293 8 13 -4097 +4098 13 17 -4270 +4271 17 21 -4465 +4466 21 35 -3870 +3871 35 155 -4119 +4120 155 @@ -14426,7 +14213,7 @@ 1 2 -50299 +50305 @@ -14447,7 +14234,7 @@ 2 3 -5968 +5969 3 @@ -14457,22 +14244,22 @@ 4 6 -3762 +3763 6 9 -4335 +4336 9 14 -4119 +4120 14 17 -4043 +4044 17 @@ -14482,7 +14269,7 @@ 21 38 -3816 +3817 38 @@ -14523,7 +14310,7 @@ 3 6 -3816 +3817 6 @@ -14533,7 +14320,7 @@ 21 24 -3232 +3233 24 @@ -14589,7 +14376,7 @@ 1 2 -44082 +44087 2 @@ -14625,7 +14412,7 @@ 6 15 -3827 +3828 15 @@ -14640,7 +14427,7 @@ 20 24 -3708 +3709 24 @@ -14650,12 +14437,12 @@ 40 50 -3351 +3352 50 77 -3405 +3406 77 @@ -14665,12 +14452,12 @@ 95 109 -3524 +3525 109 167 -1967 +1968 @@ -14680,15 +14467,15 @@ inmacroexpansion -63817054 +63823595 id -18802251 +18804178 inv -2640727 +2640998 @@ -14702,42 +14489,42 @@ 1 2 -5546048 +5546617 2 3 -3617294 +3617664 3 4 -2540866 +2541127 4 5 -1772121 +1772303 5 6 -1372368 +1372508 6 7 -900884 +900976 7 8 -1725914 +1726091 8 2023 -1326753 +1326889 @@ -14753,52 +14540,52 @@ 1 2 -622776 +622840 2 3 -366053 +366090 3 4 -192826 +192845 4 5 -224976 +224999 5 7 -201227 +201247 7 10 -222832 +222855 10 15 -214882 +214904 15 24 -199956 +199977 24 52 -199718 +199739 52 61799 -195477 +195497 @@ -14808,15 +14595,15 @@ affectedbymacroexpansion -41931290 +41935588 id -4892619 +4893121 inv -3792926 +3793315 @@ -14830,42 +14617,42 @@ 1 2 -1554422 +1554581 2 3 -944200 +944297 3 4 -794092 +794174 4 6 -397371 +397411 6 10 -384368 +384408 10 25 -396107 +396147 25 83 -366994 +367032 83 11028 -55061 +55067 @@ -14881,553 +14668,277 @@ 1 2 -222075 - - -2 -3 -298812 - - -3 -4 -257493 - - -4 -5 -333140 - - -5 -6 -328029 - - -6 -7 -310313 - - -7 -8 -257947 - - -8 -9 -247521 - - -9 -10 -210280 - - -10 -12 -318958 - - -12 -15 -289308 - - -15 -22 -302122 - - -22 -50 -286165 - - -50 -526 -130757 - - - - - - - - -macroinvocations -37324960 - - -id -37324960 - - -macro_id -77342 - - -location -693138 - - -kind -21 - - - - -id -macro_id - - -12 - - -1 -2 -37324960 - - - - - - -id -location - - -12 - - -1 -2 -37324960 - - - - - - -id -kind - - -12 - - -1 -2 -37324960 - - - - - - -macro_id -id - - -12 - - -1 -2 -17062 - - -2 -3 -10401 - - -3 -4 -4281 - - -4 -5 -6379 - - -5 -8 -5244 - - -8 -13 -6173 - - -13 -26 -5806 - - -26 -52 -5838 - - -52 -139 -5817 - - -139 -730 -5806 - - -735 -208023 -4530 - - - - - - -macro_id -location - - -12 - - -1 -2 -39887 - - -2 -3 -10758 - - -3 -4 -5503 - - -4 -5 -4671 - - -5 -9 -6379 - - -9 -27 -5860 - - -27 -3101 -4281 - - - - - - -macro_id -kind - - -12 - - -1 -2 -71849 - - -2 -3 -5492 - - - - - - -location -id - - -12 - - -1 -2 -268810 - - -2 -3 -145558 - - -3 -4 -33216 - - -4 -5 -47953 - - -5 -8 -53057 - - -8 -17 -55327 - - -17 -50 -52062 - - -50 -268738 -37151 - - - - - - -location -macro_id - - -12 - - -1 -2 -648709 - - -2 -356 -44428 - - - - - - -location -kind - - -12 - - -1 -2 -693138 - - - - - - -kind -id - - -12 - - -41293 -41294 -10 - - -3410712 -3410713 -10 - - - - - - -kind -macro_id - - -12 - - -1746 -1747 -10 - - -5915 -5916 -10 - - - - - - -kind -location - - -12 - - -5416 -5417 -10 - - -58689 -58690 -10 - - - - - - - - -macroparent -32591422 - - -id -32591422 - - -parent_id -25429927 - - - - -id -parent_id - - -12 - - -1 -2 -32591422 - - - - - - -parent_id -id - - -12 - - -1 -2 -19609707 - - -2 -3 -4988216 - - -3 -88 -832003 - - - - - - - - -macrolocationbind -16721982 - - -id -3311619 - - -location -15561858 - - - - -id -location - - -12 - - -1 -2 -770525 +222098 2 3 -536774 +298842 3 4 -750309 +257519 4 5 -385541 +333175 5 6 -188975 +328062 6 +7 +310345 + + +7 8 -251383 +257973 8 -14 -271312 +9 +247546 -14 -984 -156797 +9 +10 +210302 + + +10 +12 +318991 + + +12 +15 +289337 + + +15 +22 +302153 + + +22 +50 +286195 + + +50 +526 +130771 + + + + + + + + +macroinvocations +37328050 + + +id +37328050 + + +macro_id +77350 + + +location +693209 + + +kind +21 + + + + +id +macro_id + + +12 + + +1 +2 +37328050 + + + + + + +id +location + + +12 + + +1 +2 +37328050 + + + + + + +id +kind + + +12 + + +1 +2 +37328050 + + + + + + +macro_id +id + + +12 + + +1 +2 +17063 + + +2 +3 +10402 + + +3 +4 +4282 + + +4 +5 +6380 + + +5 +8 +5244 + + +8 +13 +6174 + + +13 +26 +5806 + + +26 +52 +5871 + + +52 +140 +5806 + + +140 +741 +5806 + + +742 +208021 +4509 + + + + + + +macro_id +location + + +12 + + +1 +2 +39891 + + +2 +3 +10759 + + +3 +4 +5504 + + +4 +5 +4671 + + +5 +9 +6380 + + +9 +27 +5861 + + +27 +3101 +4282 + + + + + + +macro_id +kind + + +12 + + +1 +2 +71856 + + +2 +3 +5493 @@ -15443,12 +14954,278 @@ 1 2 -15265413 +268838 2 -272 -296444 +3 +145573 + + +3 +4 +33219 + + +4 +5 +47958 + + +5 +8 +53062 + + +8 +17 +55333 + + +17 +50 +52067 + + +50 +268738 +37155 + + + + + + +location +macro_id + + +12 + + +1 +2 +648775 + + +2 +356 +44433 + + + + + + +location +kind + + +12 + + +1 +2 +693209 + + + + + + +kind +id + + +12 + + +41287 +41288 +10 + + +3410650 +3410651 +10 + + + + + + +kind +macro_id + + +12 + + +1746 +1747 +10 + + +5915 +5916 +10 + + + + + + +kind +location + + +12 + + +5416 +5417 +10 + + +58689 +58690 +10 + + + + + + + + +macroparent +32594568 + + +id +32594568 + + +parent_id +25432372 + + + + +id +parent_id + + +12 + + +1 +2 +32594568 + + + + + + +parent_id +id + + +12 + + +1 +2 +19611577 + + +2 +3 +4988716 + + +3 +88 +832078 + + + + + + + + +macrolocationbind +4716983 + + +id +3311958 + + +location +2340124 + + + + +id +location + + +12 + + +1 +2 +2609425 + + +2 +3 +407678 + + +3 +7 +259831 + + +7 +38 +35023 + + + + + + +location +id + + +12 + + +1 +2 +1864158 + + +2 +3 +204303 + + +3 +8 +186131 + + +8 +452 +85530 @@ -15458,11 +15235,11 @@ macro_argument_unexpanded -96605182 +96614771 invocation -28234388 +28237098 argument_index @@ -15470,7 +15247,7 @@ text -300664 +300695 @@ -15484,22 +15261,22 @@ 1 2 -7874290 +7875043 2 3 -11879289 +11880377 3 4 -6423862 +6424521 4 67 -2056946 +2057157 @@ -15515,22 +15292,22 @@ 1 2 -7933175 +7933934 2 3 -12052852 +12053958 3 4 -6241065 +6241705 4 67 -2007294 +2007500 @@ -15555,7 +15332,7 @@ 784349 -2611263 +2611246 32 @@ -15598,57 +15375,57 @@ 1 2 -40611 +40616 2 3 -46807 +46812 3 4 -12445 +12446 4 5 -54549 +54554 5 8 -20143 +20145 8 12 -16737 +16739 12 16 -19213 +19215 16 25 -25204 +25206 25 46 -22587 +22611 46 -160 -22565 +161 +22730 -160 +161 597776 -19797 +19615 @@ -15664,17 +15441,17 @@ 1 2 -221224 +221247 2 3 -69178 +69185 3 9 -10261 +10262 @@ -15684,11 +15461,11 @@ macro_argument_expanded -96605182 +96614771 invocation -28234388 +28237098 argument_index @@ -15696,7 +15473,7 @@ text -180234 +180252 @@ -15710,22 +15487,22 @@ 1 2 -7874290 +7875043 2 3 -11879289 +11880377 3 4 -6423862 +6424521 4 67 -2056946 +2057157 @@ -15741,22 +15518,22 @@ 1 2 -11483096 +11484197 2 3 -10298592 +10299540 3 4 -5379706 +5380257 4 9 -1072993 +1073103 @@ -15781,7 +15558,7 @@ 784349 -2611263 +2611246 32 @@ -15824,12 +15601,12 @@ 1 2 -25971 +25974 2 3 -35908 +35912 3 @@ -15839,7 +15616,7 @@ 4 5 -16997 +16999 5 @@ -15849,37 +15626,37 @@ 6 7 -14629 +14630 7 11 -15170 +15171 11 16 -12964 +12965 16 29 -14229 +14230 29 72 -13526 +13527 72 249 -13537 +13538 249 -1174337 -9363 +1174335 +9364 @@ -15895,17 +15672,17 @@ 1 2 -94447 +94457 2 3 -71579 +71586 3 5 -13742 +13744 5 @@ -15920,15 +15697,15 @@ functions -3598577 +3598946 id -3598577 +3598946 name -259609 +259635 kind @@ -15946,7 +15723,7 @@ 1 2 -3598577 +3598946 @@ -15962,7 +15739,7 @@ 1 2 -3598577 +3598946 @@ -15978,32 +15755,32 @@ 1 2 -169270 +169287 2 3 -25279 +25282 3 4 -14164 +14165 4 7 -19959 +19962 7 24 -19527 +19529 24 62655 -11407 +11408 @@ -16019,7 +15796,7 @@ 1 2 -258127 +258154 2 @@ -16126,15 +15903,15 @@ function_entry_point -1236446 +1236573 id -1171950 +1172070 entry_point -1236436 +1236562 @@ -16148,7 +15925,7 @@ 1 2 -1164229 +1164349 2 @@ -16169,7 +15946,7 @@ 1 2 -1236425 +1236552 2 @@ -16184,15 +15961,15 @@ function_return_type -3647925 +3648299 id -3598555 +3598924 return_type -852331 +852418 @@ -16206,12 +15983,12 @@ 1 2 -3552818 +3553182 2 6 -45737 +45741 @@ -16227,22 +16004,22 @@ 1 2 -413363 +413405 2 3 -320808 +320841 3 5 -67589 +67596 5 119025 -50570 +50575 @@ -16263,49 +16040,49 @@ function_deleted -68594 +68926 id -68594 +68926 function_defaulted -20046 +20048 id -20046 +20048 fun_decls -2988576 +2988883 id -2988576 +2988883 function -2781462 +2781747 type_id -707259 +707331 name -231647 +231671 location -658343 +658410 @@ -16319,7 +16096,7 @@ 1 2 -2988576 +2988883 @@ -16335,7 +16112,7 @@ 1 2 -2988576 +2988883 @@ -16351,7 +16128,7 @@ 1 2 -2988576 +2988883 @@ -16367,7 +16144,7 @@ 1 2 -2988576 +2988883 @@ -16383,12 +16160,12 @@ 1 2 -2610634 +2610902 2 29 -170827 +170844 @@ -16404,12 +16181,12 @@ 1 2 -2745943 +2746224 2 6 -35519 +35522 @@ -16425,7 +16202,7 @@ 1 2 -2781462 +2781747 @@ -16441,12 +16218,12 @@ 1 2 -2691545 +2691821 2 29 -89917 +89926 @@ -16462,22 +16239,22 @@ 1 2 -303983 +304015 2 3 -297918 +297948 3 5 -64572 +64579 5 93959 -40784 +40789 @@ -16493,22 +16270,22 @@ 1 2 -318180 +318213 2 3 -296534 +296564 3 5 -56236 +56241 5 87830 -36308 +36312 @@ -16524,17 +16301,17 @@ 1 2 -603026 +603088 2 4 -63026 +63032 4 7436 -41206 +41210 @@ -16550,22 +16327,22 @@ 1 2 -560165 +560222 2 3 -68270 +68277 3 6 -57663 +57669 6 18812 -21160 +21162 @@ -16581,32 +16358,32 @@ 1 2 -132010 +132023 2 3 -31269 +31273 3 4 -17062 +17063 4 6 -17656 +17658 6 13 -18727 +18729 13 62323 -14921 +14922 @@ -16622,32 +16399,32 @@ 1 2 -145288 +145303 2 3 -29150 +29153 3 4 -14867 +14868 4 7 -18630 +18631 7 32 -17408 +17409 32 61959 -6303 +6304 @@ -16663,17 +16440,17 @@ 1 2 -203070 +203091 2 5 -18543 +18545 5 32311 -10034 +10035 @@ -16689,22 +16466,22 @@ 1 2 -150380 +150396 2 3 -45380 +45384 3 5 -18240 +18242 5 133 -17375 +17377 134 @@ -16725,27 +16502,27 @@ 1 2 -421527 +421570 2 3 -106406 +106417 3 5 -60604 +60610 5 18 -50148 +50153 18 1219 -19657 +19659 @@ -16761,27 +16538,27 @@ 1 2 -432080 +432124 2 3 -117899 +117912 3 6 -51748 +51754 6 64 -49434 +49440 64 1219 -7179 +7180 @@ -16797,17 +16574,17 @@ 1 2 -566642 +566700 2 4 -58366 +58372 4 611 -33335 +33338 @@ -16823,12 +16600,12 @@ 1 2 -634490 +634556 2 126 -23852 +23854 @@ -16838,11 +16615,11 @@ fun_def -574513 +574897 id -574513 +574897 @@ -16871,11 +16648,11 @@ fun_decl_specifiers -660231 +660299 id -370231 +370269 name @@ -16893,17 +16670,17 @@ 1 2 -108034 +108045 2 3 -234393 +234417 3 4 -27803 +27806 @@ -17060,11 +16837,11 @@ fun_decl_empty_throws -707875 +707948 fun_decl -707875 +707948 @@ -17079,7 +16856,7 @@ constant -6714 +6715 @@ -17114,7 +16891,7 @@ 1 2 -6692 +6693 2 @@ -17129,11 +16906,11 @@ fun_decl_empty_noexcept -440968 +441013 fun_decl -440968 +441013 @@ -17188,11 +16965,11 @@ param_decl_bind -3717623 +3718004 id -3717623 +3718004 index @@ -17200,7 +16977,7 @@ fun_decl -2374824 +2375067 @@ -17214,7 +16991,7 @@ 1 2 -3717623 +3718004 @@ -17230,7 +17007,7 @@ 1 2 -3717623 +3718004 @@ -17378,22 +17155,22 @@ 1 2 -1634673 +1634840 2 3 -416455 +416498 3 4 -192755 +192775 4 33 -130939 +130953 @@ -17409,22 +17186,22 @@ 1 2 -1634673 +1634840 2 3 -416455 +416498 3 4 -192755 +192775 4 33 -130939 +130953 @@ -17434,27 +17211,27 @@ var_decls -4558796 +4559414 id -4558796 +4559414 variable -4255471 +4255908 type_id -1378967 +1381433 name -126863 +126876 location -1159212 +1159331 @@ -17468,7 +17245,7 @@ 1 2 -4558796 +4559414 @@ -17484,7 +17261,7 @@ 1 2 -4558796 +4559414 @@ -17500,7 +17277,7 @@ 1 2 -4558796 +4559414 @@ -17516,7 +17293,7 @@ 1 2 -4558796 +4559414 @@ -17532,12 +17309,12 @@ 1 2 -4009908 +4010167 2 9 -245563 +245740 @@ -17553,12 +17330,12 @@ 1 2 -4199949 +4200228 2 9 -55522 +55679 @@ -17574,12 +17351,12 @@ 1 2 -4237782 +4238216 2 3 -17689 +17691 @@ -17595,12 +17372,12 @@ 1 2 -4142350 +4142775 2 9 -113120 +113132 @@ -17616,27 +17393,27 @@ 1 2 -896954 +898949 2 3 -262452 +262663 3 5 -107119 +107347 5 -39 -103540 +40 +103670 -39 -8447 -8898 +40 +8448 +8802 @@ -17652,22 +17429,22 @@ 1 2 -920666 +922664 2 3 -265772 +265983 3 6 -111531 +111791 6 -7871 -80996 +7872 +80994 @@ -17683,17 +17460,17 @@ 1 2 -1205609 +1207679 2 3 -116829 +117220 3 1005 -56527 +56533 @@ -17709,22 +17486,22 @@ 1 2 -1098208 +1100278 2 3 -137513 +137863 3 8 -104276 +104340 8 -3994 -38968 +3995 +38950 @@ -17740,37 +17517,37 @@ 1 2 -49175 +49180 2 3 -23841 +23844 3 4 -10780 +10781 4 6 -11212 +11213 6 9 -9547 +9548 9 21 -9925 +9926 21 149 -9525 +9526 149 @@ -17791,37 +17568,37 @@ 1 2 -53057 +53062 2 3 -22803 +22805 3 4 -11569 +11570 4 6 -10271 +10272 6 10 -9633 +9634 10 25 -9785 +9786 25 2447 -9515 +9516 2450 @@ -17842,32 +17619,32 @@ 1 2 -78272 +78215 2 3 -16316 +16328 3 4 -8228 +8240 4 7 -10693 +10694 7 31 -9525 +9537 31 60365 -3827 +3860 @@ -17883,32 +17660,32 @@ 1 2 -73676 +73684 2 3 -19797 +19799 3 4 -6844 +6845 4 7 -11093 +11094 7 22 -9655 +9656 22 9262 -5795 +5796 @@ -17924,27 +17701,27 @@ 1 2 -819147 +819231 2 3 -144931 +144946 3 5 -96804 +96814 5 39 -87224 +87233 39 64244 -11104 +11105 @@ -17960,22 +17737,22 @@ 1 2 -856148 +856235 2 3 -143871 +143886 3 7 -95604 +95614 7 63840 -63588 +63595 @@ -17991,17 +17768,17 @@ 1 2 -1006258 +1005983 2 4 -100837 +101053 4 52429 -52116 +52294 @@ -18017,12 +17794,12 @@ 1 2 -1150584 +1150702 2 52 -8628 +8629 @@ -18032,22 +17809,22 @@ var_def -1689762 +1690411 id -1689762 +1690411 var_decl_specifiers -349131 +349167 id -349131 +349167 name @@ -18065,7 +17842,7 @@ 1 2 -349131 +349167 @@ -18106,19 +17883,19 @@ type_decls -1355244 +1355383 id -1355244 +1355383 type_id -1327812 +1327949 location -1051314 +1051422 @@ -18132,7 +17909,7 @@ 1 2 -1355244 +1355383 @@ -18148,7 +17925,7 @@ 1 2 -1355244 +1355383 @@ -18164,12 +17941,12 @@ 1 2 -1307507 +1307641 2 24 -20305 +20308 @@ -18185,12 +17962,12 @@ 1 2 -1308566 +1308700 2 24 -19246 +19248 @@ -18206,12 +17983,12 @@ 1 2 -991910 +992012 2 588 -59404 +59410 @@ -18227,12 +18004,12 @@ 1 2 -992970 +993071 2 588 -58344 +58350 @@ -18242,33 +18019,33 @@ type_def -900036 +900128 id -900036 +900128 type_decl_top -259284 +259311 type_decl -259284 +259311 namespace_decls -127544 +127557 id -127544 +127557 namespace_id @@ -18276,11 +18053,11 @@ location -113099 +113110 bodylocation -113412 +113424 @@ -18294,7 +18071,7 @@ 1 2 -127544 +127557 @@ -18310,7 +18087,7 @@ 1 2 -127544 +127557 @@ -18326,7 +18103,7 @@ 1 2 -127544 +127557 @@ -18342,7 +18119,7 @@ 1 2 -3935 +3936 2 @@ -18388,7 +18165,7 @@ 1 2 -3935 +3936 2 @@ -18434,7 +18211,7 @@ 1 2 -3935 +3936 2 @@ -18480,12 +18257,12 @@ 1 2 -104957 +104968 2 29 -8141 +8142 @@ -18501,12 +18278,12 @@ 1 2 -104957 +104968 2 29 -8141 +8142 @@ -18522,7 +18299,7 @@ 1 2 -112385 +112397 2 @@ -18543,12 +18320,12 @@ 1 2 -105616 +105627 2 29 -7795 +7796 @@ -18564,12 +18341,12 @@ 1 2 -105616 +105627 2 29 -7795 +7796 @@ -18585,7 +18362,7 @@ 1 2 -113034 +113045 2 @@ -18600,19 +18377,19 @@ usings -197383 +197403 id -197383 +197403 element_id -19624 +19626 location -20035 +20037 @@ -18626,7 +18403,7 @@ 1 2 -197383 +197403 @@ -18642,7 +18419,7 @@ 1 2 -197383 +197403 @@ -18658,12 +18435,12 @@ 1 2 -14542 +14544 2 3 -2086 +2087 3 @@ -18673,7 +18450,7 @@ 60 116 -1502 +1503 @@ -18689,12 +18466,12 @@ 1 2 -14542 +14544 2 3 -2086 +2087 3 @@ -18704,7 +18481,7 @@ 60 116 -1502 +1503 @@ -18720,7 +18497,7 @@ 1 2 -16316 +16317 2 @@ -18751,7 +18528,7 @@ 1 2 -16316 +16317 2 @@ -18776,15 +18553,15 @@ using_container -613774 +613837 parent -11223 +11224 child -194777 +194797 @@ -18798,7 +18575,7 @@ 1 2 -5027 +5028 2 @@ -18844,27 +18621,27 @@ 1 2 -22760 +22762 2 3 -15624 +15625 3 4 -117445 +117457 4 5 -24847 +24849 5 128 -14099 +14101 @@ -19135,15 +18912,15 @@ params -4527645 +4528109 id -4460380 +4460837 function -2804460 +2804748 index @@ -19151,7 +18928,7 @@ type_id -1294942 +1295075 @@ -19165,12 +18942,12 @@ 1 2 -4459742 +4460199 2 65 -637 +638 @@ -19186,7 +18963,7 @@ 1 2 -4460380 +4460837 @@ -19202,12 +18979,12 @@ 1 2 -4398727 +4399178 2 9 -61653 +61659 @@ -19223,22 +19000,22 @@ 1 2 -1853540 +1853730 2 3 -546822 +546878 3 4 -253294 +253320 4 33 -150802 +150818 @@ -19254,22 +19031,22 @@ 1 2 -1853540 +1853730 2 3 -546822 +546878 3 4 -253294 +253320 4 33 -150802 +150818 @@ -19285,22 +19062,22 @@ 1 2 -1949080 +1949280 2 3 -543146 +543202 3 4 -210022 +210044 4 20 -102210 +102221 @@ -19514,27 +19291,27 @@ 1 2 -816130 +816214 2 3 -249791 +249817 3 5 -101151 +101161 5 16 -98286 +98296 16 6399 -29583 +29586 @@ -19550,27 +19327,27 @@ 1 2 -837345 +837430 2 3 -237735 +237759 3 5 -100351 +100361 5 19 -98869 +98880 19 6014 -20641 +20643 @@ -19586,17 +19363,17 @@ 1 2 -1136852 +1136969 2 3 -126863 +126876 3 33 -31226 +31229 @@ -19606,15 +19383,15 @@ overrides -81074 +81069 new -78173 +78172 old -25204 +25203 @@ -19628,7 +19405,7 @@ 1 2 -75394 +75393 2 @@ -19664,7 +19441,7 @@ 4 8 -1952 +1951 8 @@ -19679,19 +19456,19 @@ membervariables -344801 +344836 id -344801 +344836 type_id -161658 +161674 name -52051 +52056 @@ -19705,7 +19482,7 @@ 1 2 -344801 +344836 @@ -19721,7 +19498,7 @@ 1 2 -344801 +344836 @@ -19737,17 +19514,17 @@ 1 2 -135437 +135451 2 3 -13104 +13106 3 22 -12142 +12143 22 @@ -19768,17 +19545,17 @@ 1 2 -144552 +144567 2 4 -13288 +13289 4 335 -3816 +3817 @@ -19794,12 +19571,12 @@ 1 2 -26782 +26785 2 3 -8563 +8564 3 @@ -19809,7 +19586,7 @@ 4 6 -3697 +3698 6 @@ -19819,7 +19596,7 @@ 13 2081 -3881 +3882 @@ -19835,22 +19612,22 @@ 1 2 -33508 +33511 2 3 -6822 +6823 3 4 -3070 +3071 4 7 -4465 +4466 7 @@ -19870,11 +19647,11 @@ globalvariables -336971 +337006 id -336971 +337006 type_id @@ -19882,7 +19659,7 @@ name -330309 +330343 @@ -19896,7 +19673,7 @@ 1 2 -336971 +337006 @@ -19912,7 +19689,7 @@ 1 2 -336971 +337006 @@ -20000,12 +19777,12 @@ 1 2 -326178 +326211 2 30 -4130 +4131 @@ -20021,7 +19798,7 @@ 1 2 -329606 +329640 2 @@ -20036,19 +19813,19 @@ localvariables -668696 +668764 id -668696 +668764 type_id -39231 +39235 name -56589 +56595 @@ -20062,7 +19839,7 @@ 1 2 -668696 +668764 @@ -20078,7 +19855,7 @@ 1 2 -668696 +668764 @@ -20094,22 +19871,22 @@ 1 2 -15271 +15273 2 3 -9848 +9849 3 4 -2055 +2056 4 5 -4432 +4433 5 @@ -20140,17 +19917,17 @@ 1 2 -29602 +29605 2 3 -5204 +5205 3 7 -3076 +3077 7 @@ -20171,12 +19948,12 @@ 1 2 -33821 +33825 2 3 -9048 +9049 3 @@ -20191,7 +19968,7 @@ 7 33 -4277 +4278 33 @@ -20212,17 +19989,17 @@ 1 2 -45498 +45503 2 3 -5868 +5869 3 11 -4338 +4339 11 @@ -20305,11 +20082,11 @@ enumconstants -94952 +94956 id -94952 +94956 parent @@ -20325,11 +20102,11 @@ name -62469 +62473 location -74428 +74432 @@ -20343,7 +20120,7 @@ 1 2 -94952 +94956 @@ -20359,7 +20136,7 @@ 1 2 -94952 +94956 @@ -20375,7 +20152,7 @@ 1 2 -94952 +94956 @@ -20391,7 +20168,7 @@ 1 2 -94952 +94956 @@ -20407,7 +20184,7 @@ 1 2 -94952 +94956 @@ -20428,7 +20205,7 @@ 2 3 -1618 +1617 3 @@ -20438,7 +20215,7 @@ 4 5 -1205 +1206 5 @@ -20484,7 +20261,7 @@ 2 3 -1745 +1744 3 @@ -20494,7 +20271,7 @@ 4 5 -1165 +1166 5 @@ -20504,12 +20281,12 @@ 7 12 -1081 +1080 12 6696 -973 +974 @@ -20551,7 +20328,7 @@ 2 3 -1746 +1745 3 @@ -20561,7 +20338,7 @@ 4 5 -1164 +1165 5 @@ -20571,12 +20348,12 @@ 7 12 -1082 +1081 12 6696 -973 +974 @@ -20597,7 +20374,7 @@ 2 3 -1631 +1630 3 @@ -20607,7 +20384,7 @@ 4 5 -1208 +1209 5 @@ -20803,7 +20580,7 @@ 2 3 -1523 +1522 3 @@ -20813,7 +20590,7 @@ 4 5 -1114 +1115 5 @@ -20875,7 +20652,7 @@ 2 3 -1647 +1646 3 @@ -20885,7 +20662,7 @@ 4 5 -1070 +1071 5 @@ -20895,12 +20672,12 @@ 7 12 -984 +983 12 6696 -870 +871 @@ -20921,7 +20698,7 @@ 2 3 -1648 +1647 3 @@ -20931,7 +20708,7 @@ 4 5 -1069 +1070 5 @@ -20941,12 +20718,12 @@ 7 12 -985 +984 12 12424 -870 +871 @@ -20967,7 +20744,7 @@ 2 3 -1534 +1533 3 @@ -20977,7 +20754,7 @@ 4 5 -1117 +1118 5 @@ -21013,7 +20790,7 @@ 1 2 -47451 +47455 2 @@ -21039,7 +20816,7 @@ 1 2 -55336 +55340 2 @@ -21065,7 +20842,7 @@ 1 2 -60740 +60744 2 @@ -21086,7 +20863,7 @@ 1 2 -49390 +49394 2 @@ -21112,7 +20889,7 @@ 1 2 -48929 +48933 2 @@ -21138,7 +20915,7 @@ 1 2 -67945 +67949 2 @@ -21164,7 +20941,7 @@ 1 2 -70248 +70252 2 @@ -21185,7 +20962,7 @@ 1 2 -71720 +71724 2 @@ -21206,7 +20983,7 @@ 1 2 -64192 +64196 2 @@ -21232,7 +21009,7 @@ 1 2 -71874 +71878 2 @@ -21271,7 +21048,7 @@ alignment -64 +54 @@ -21523,19 +21300,24 @@ 12 -2 -3 +1 +2 10 -3 -4 +2 +3 10 4 5 -21 +10 + + +6 +7 +10 9 @@ -21564,19 +21346,24 @@ 12 -2 -3 +1 +2 10 -3 -4 +2 +3 10 4 5 -21 +10 + + +6 +7 +10 9 @@ -21605,19 +21392,24 @@ 12 -2 -3 +1 +2 10 -3 -4 +2 +3 10 4 5 -21 +10 + + +6 +7 +10 9 @@ -21669,12 +21461,12 @@ 1 2 -43 +54 2 3 -32 +21 @@ -21789,12 +21581,7 @@ 5 6 -21 - - -6 -7 -10 +32 @@ -21808,18 +21595,13 @@ 12 -2 -3 -10 - - 4 5 10 -5 -6 +7 +8 10 @@ -21849,18 +21631,13 @@ 12 -2 -3 -10 - - 4 5 10 -5 -6 +7 +8 10 @@ -21890,18 +21667,13 @@ 12 -2 -3 -10 - - 4 5 10 -5 -6 +7 +8 10 @@ -21933,7 +21705,7 @@ 1 2 -21 +10 2 @@ -21952,11 +21724,6 @@ 12 -1 -2 -10 - - 3 4 54 @@ -21969,15 +21736,15 @@ derivedtypes -3507081 +3507440 id -3507081 +3507440 name -1371592 +1371733 kind @@ -21985,7 +21752,7 @@ type_id -2230682 +2230910 @@ -21999,7 +21766,7 @@ 1 2 -3507081 +3507440 @@ -22015,7 +21782,7 @@ 1 2 -3507081 +3507440 @@ -22031,7 +21798,7 @@ 1 2 -3507081 +3507440 @@ -22047,17 +21814,17 @@ 1 2 -956650 +956748 2 3 -334637 +334671 3 58537 -80304 +80313 @@ -22073,7 +21840,7 @@ 1 2 -1371571 +1371711 2 @@ -22094,17 +21861,17 @@ 1 2 -956650 +956748 2 3 -334637 +334671 3 58519 -80304 +80313 @@ -22273,22 +22040,22 @@ 1 2 -1407760 +1407905 2 3 -465750 +465798 3 4 -272054 +272082 4 210 -85116 +85125 @@ -22304,22 +22071,22 @@ 1 2 -1408917 +1409062 2 3 -465599 +465646 3 4 -271005 +271033 4 210 -85159 +85168 @@ -22335,22 +22102,22 @@ 1 2 -1409447 +1409592 2 3 -466896 +466944 3 4 -270681 +270709 4 7 -83656 +83665 @@ -22360,11 +22127,11 @@ pointerishsize -2536028 +2536288 id -2536028 +2536288 size @@ -22386,7 +22153,7 @@ 1 2 -2536028 +2536288 @@ -22402,7 +22169,7 @@ 1 2 -2536028 +2536288 @@ -22481,11 +22248,11 @@ arraysizes -18262 +18264 id -18262 +18264 num_elements @@ -22493,7 +22260,7 @@ bytesize -2713 +2714 alignment @@ -22511,7 +22278,7 @@ 1 2 -18262 +18264 @@ -22527,7 +22294,7 @@ 1 2 -18262 +18264 @@ -22543,7 +22310,7 @@ 1 2 -18262 +18264 @@ -22912,15 +22679,15 @@ typedefbase -1559774 +1559934 id -1559774 +1559934 type_id -742129 +742205 @@ -22934,7 +22701,7 @@ 1 2 -1559774 +1559934 @@ -22950,22 +22717,22 @@ 1 2 -575681 +575740 2 3 -72087 +72094 3 6 -63804 +63811 6 4377 -30556 +30559 @@ -22975,19 +22742,19 @@ decltypes -63437 +63443 id -63437 +63443 expr -60042 +60048 base_type -5827 +5828 parentheses_would_change_meaning @@ -23005,7 +22772,7 @@ 1 2 -63437 +63443 @@ -23021,7 +22788,7 @@ 1 2 -63437 +63443 @@ -23037,7 +22804,7 @@ 1 2 -63437 +63443 @@ -23053,7 +22820,7 @@ 1 2 -56668 +56674 2 @@ -23074,7 +22841,7 @@ 1 2 -56668 +56674 2 @@ -23095,7 +22862,7 @@ 1 2 -60042 +60048 @@ -23173,7 +22940,7 @@ 1 2 -5827 +5828 @@ -23246,15 +23013,15 @@ usertypes -3306162 +3306501 id -3306162 +3306501 name -590613 +590674 kind @@ -23272,7 +23039,7 @@ 1 2 -3306162 +3306501 @@ -23288,7 +23055,7 @@ 1 2 -3306162 +3306501 @@ -23304,22 +23071,22 @@ 1 2 -400539 +400580 2 3 -115899 +115911 3 7 -45607 +45611 7 27007 -28566 +28569 @@ -23335,12 +23102,12 @@ 1 2 -546520 +546576 2 9 -44093 +44098 @@ -23472,11 +23239,11 @@ usertypesize -854699 +854786 id -854699 +854786 size @@ -23498,7 +23265,7 @@ 1 2 -854699 +854786 @@ -23514,7 +23281,7 @@ 1 2 -854699 +854786 @@ -23757,48 +23524,59 @@ is_pod_class -574102 +574161 + + +id +574161 + + + + + +is_standard_layout_class +647413 id -574102 +647413 is_complete -854699 +854786 id -854699 +854786 is_class_template -166815 +166833 id -166815 +166833 class_instantiation -664138 +664207 to -663501 +663569 from -55998 +56003 @@ -23812,7 +23590,7 @@ 1 2 -662906 +662974 2 @@ -23833,17 +23611,17 @@ 1 2 -17700 +17701 2 3 -10066 +10067 3 4 -6119 +6120 4 @@ -23858,17 +23636,17 @@ 7 10 -4227 +4228 10 19 -4270 +4271 19 155 -4227 +4228 155 @@ -23883,11 +23661,11 @@ class_template_argument -1681653 +1681825 type_id -820639 +820723 index @@ -23895,7 +23673,7 @@ arg_type -701593 +701665 @@ -23909,22 +23687,22 @@ 1 2 -369875 +369913 2 3 -282931 +282960 3 4 -104135 +104146 4 21 -62907 +62913 21 @@ -23945,22 +23723,22 @@ 1 2 -385305 +385344 2 3 -282834 +282863 3 4 -101929 +101940 4 41 -50570 +50575 @@ -24138,22 +23916,22 @@ 1 2 -465663 +465711 2 3 -147948 +147963 3 6 -57144 +57150 6 2531 -30837 +30840 @@ -24169,17 +23947,17 @@ 1 2 -632609 +632674 2 3 -60312 +60318 3 22 -8671 +8672 @@ -24237,11 +24015,11 @@ type_mentions -7732581 +7732644 id -7732581 +7732644 type_id @@ -24249,7 +24027,7 @@ location -1848637 +1848640 kind @@ -24267,7 +24045,7 @@ 1 2 -7732581 +7732644 @@ -24283,7 +24061,7 @@ 1 2 -7732581 +7732644 @@ -24299,7 +24077,7 @@ 1 2 -7732581 +7732644 @@ -24315,12 +24093,12 @@ 1 2 -19043 +19039 2 3 -9907 +9909 3 @@ -24330,22 +24108,22 @@ 4 5 -5243 +5240 5 7 -7050 +7049 7 11 -8098 +8104 11 17 -7168 +7166 17 @@ -24360,7 +24138,7 @@ 49 123 -6735 +6737 123 @@ -24381,42 +24159,42 @@ 1 2 -31277 +31275 2 3 -13535 +13533 3 4 -7042 +7044 4 6 -8006 +8004 6 8 -6510 +6512 8 13 -7314 +7316 13 24 -6904 +6903 24 87 -6766 +6767 87 @@ -24463,7 +24241,7 @@ 2 3 -149403 +149406 3 @@ -24499,12 +24277,12 @@ 1 2 -1753149 +1753105 2 14 -95488 +95535 @@ -24520,7 +24298,7 @@ 1 2 -1848635 +1848638 2 @@ -24544,8 +24322,8 @@ 1 -7635373 -7635374 +7635379 +7635380 1 @@ -24586,8 +24364,8 @@ 1 -1842179 -1842180 +1842182 +1842183 1 @@ -24598,26 +24376,26 @@ is_function_template -929078 +929173 id -929078 +929173 function_instantiation -1714123 +1714299 to -1714123 +1714299 from -220402 +220425 @@ -24631,7 +24409,7 @@ 1 2 -1714123 +1714299 @@ -24647,32 +24425,32 @@ 1 2 -95366 +95376 2 3 -51802 +51808 3 4 -12985 +12987 4 6 -18521 +18523 6 11 -18705 +18707 11 49 -16575 +16577 49 @@ -24687,11 +24465,11 @@ function_template_argument -1828650 +1828837 function_id -1051022 +1051130 index @@ -24699,7 +24477,7 @@ arg_type -337502 +337537 @@ -24713,22 +24491,22 @@ 1 2 -595922 +595983 2 3 -336691 +336726 3 5 -89852 +89861 5 21 -28555 +28558 @@ -24744,22 +24522,22 @@ 1 2 -608670 +608733 2 3 -317142 +317175 3 5 -96556 +96565 5 21 -28653 +28656 @@ -24997,27 +24775,27 @@ 1 2 -223235 +223258 2 3 -44320 +44325 3 6 -26512 +26515 6 15 -25593 +25595 15 927 -17840 +17842 @@ -25033,12 +24811,12 @@ 1 2 -308741 +308773 2 4 -27442 +27445 4 @@ -25053,26 +24831,26 @@ is_variable_template -25247 +25249 id -25247 +25249 variable_instantiation -25528 +25531 to -25528 +25531 from -5373 +5374 @@ -25086,7 +24864,7 @@ 1 2 -25528 +25531 @@ -25298,15 +25076,15 @@ routinetypes -508632 +508685 id -508632 +508685 return_type -242601 +242625 @@ -25320,7 +25098,7 @@ 1 2 -508632 +508685 @@ -25336,17 +25114,17 @@ 1 2 -201340 +201361 2 3 -24717 +24720 3 9044 -16543 +16544 @@ -25356,11 +25134,11 @@ routinetypeargs -816239 +816322 routine -392798 +392838 index @@ -25368,7 +25146,7 @@ type_id -253792 +253818 @@ -25382,27 +25160,27 @@ 1 2 -185218 +185237 2 3 -104860 +104870 3 4 -59869 +59875 4 6 -32653 +32657 6 33 -10196 +10197 @@ -25418,22 +25196,22 @@ 1 2 -211417 +211439 2 3 -105789 +105800 3 4 -50862 +50867 4 27 -24728 +24730 @@ -25611,27 +25389,27 @@ 1 2 -161820 +161837 2 3 -41195 +41200 3 4 -17148 +17150 4 7 -19343 +19345 7 1102 -14283 +14284 @@ -25647,17 +25425,17 @@ 1 2 -196485 +196505 2 3 -44872 +44876 3 33 -12434 +12435 @@ -25667,19 +25445,19 @@ ptrtomembers -13331 +13333 id -13331 +13333 type_id -11450 +11451 class_id -7406 +7407 @@ -25693,7 +25471,7 @@ 1 2 -13331 +13333 @@ -25709,7 +25487,7 @@ 1 2 -13331 +13333 @@ -25725,7 +25503,7 @@ 1 2 -11223 +11224 2 @@ -25746,7 +25524,7 @@ 1 2 -11223 +11224 2 @@ -25767,7 +25545,7 @@ 1 2 -6595 +6596 2 @@ -25793,7 +25571,7 @@ 1 2 -6595 +6596 2 @@ -25861,11 +25639,11 @@ typespecifiers -1079492 +1079602 type_id -1075058 +1075169 spec_id @@ -25883,7 +25661,7 @@ 1 2 -1070625 +1070735 2 @@ -25944,11 +25722,11 @@ funspecifiers -9510952 +9511927 func_id -3553413 +3553777 spec_id @@ -25966,22 +25744,22 @@ 1 2 -343492 +343528 2 3 -923218 +923312 3 4 -1829882 +1830070 4 5 -452742 +452789 5 @@ -26077,11 +25855,11 @@ varspecifiers -1131691 +1131807 var_id -1038886 +1038992 spec_id @@ -26099,12 +25877,12 @@ 1 2 -946081 +946178 2 3 -92804 +92814 @@ -26160,11 +25938,11 @@ attributes -426276 +426319 id -426276 +426319 kind @@ -26180,7 +25958,7 @@ location -416886 +416928 @@ -26194,7 +25972,7 @@ 1 2 -426276 +426319 @@ -26210,7 +25988,7 @@ 1 2 -426276 +426319 @@ -26226,7 +26004,7 @@ 1 2 -426276 +426319 @@ -26242,7 +26020,7 @@ 1 2 -426276 +426319 @@ -26640,12 +26418,12 @@ 1 2 -409474 +409516 2 13 -7411 +7412 @@ -26661,7 +26439,7 @@ 1 2 -416886 +416928 @@ -26677,7 +26455,7 @@ 1 2 -415772 +415814 2 @@ -26698,7 +26476,7 @@ 1 2 -416886 +416928 @@ -26708,11 +26486,11 @@ attribute_args -137094 +137108 id -137094 +137108 kind @@ -26720,7 +26498,7 @@ attribute -134927 +134941 index @@ -26728,7 +26506,7 @@ location -86689 +86698 @@ -26742,7 +26520,7 @@ 1 2 -137094 +137108 @@ -26758,7 +26536,7 @@ 1 2 -137094 +137108 @@ -26774,7 +26552,7 @@ 1 2 -137094 +137108 @@ -26790,7 +26568,7 @@ 1 2 -137094 +137108 @@ -26890,12 +26668,12 @@ 1 2 -133799 +133812 2 8 -1128 +1129 @@ -26911,7 +26689,7 @@ 1 2 -134103 +134117 2 @@ -26932,12 +26710,12 @@ 1 2 -133799 +133812 2 8 -1128 +1129 @@ -26953,7 +26731,7 @@ 1 2 -133851 +133865 2 @@ -27103,12 +26881,12 @@ 1 2 -51052 +51057 2 3 -31516 +31520 3 @@ -27129,7 +26907,7 @@ 1 2 -86681 +86690 2 @@ -27150,12 +26928,12 @@ 1 2 -51027 +51032 2 3 -31566 +31569 3 @@ -27176,7 +26954,7 @@ 1 2 -86659 +86668 3 @@ -27191,15 +26969,15 @@ attribute_arg_value -137053 +137067 arg -137053 +137067 value -30008 +30011 @@ -27213,7 +26991,7 @@ 1 2 -137053 +137067 @@ -27229,7 +27007,7 @@ 1 2 -29422 +29425 2 @@ -27355,15 +27133,15 @@ typeattributes -8390 +8391 type_id -7795 +7796 spec_id -8390 +8391 @@ -27377,7 +27155,7 @@ 1 2 -7536 +7537 2 @@ -27398,7 +27176,7 @@ 1 2 -8390 +8391 @@ -27408,15 +27186,15 @@ funcattributes -252883 +252909 func_id -133102 +133116 spec_id -252883 +252909 @@ -27430,17 +27208,17 @@ 1 2 -67081 +67087 2 3 -13591 +13592 3 4 -51575 +51581 4 @@ -27461,7 +27239,7 @@ 1 2 -252883 +252909 @@ -27471,15 +27249,15 @@ varattributes -407665 +407707 var_id -351962 +351998 spec_id -407665 +407707 @@ -27493,12 +27271,12 @@ 1 2 -296275 +296305 2 3 -55685 +55691 14 @@ -27519,7 +27297,7 @@ 1 2 -407665 +407707 @@ -27577,15 +27355,15 @@ unspecifiedtype -7347405 +7348158 type_id -7347405 +7348158 unspecified_type_id -3871366 +3871763 @@ -27599,7 +27377,7 @@ 1 2 -7347405 +7348158 @@ -27615,22 +27393,22 @@ 1 2 -2174554 +2174777 2 3 -1339933 +1340071 3 8 -302459 +302490 8 6073 -54419 +54425 @@ -27640,11 +27418,11 @@ member -4492882 +4493343 parent -444719 +444765 index @@ -27652,7 +27430,7 @@ child -4451330 +4451786 @@ -27666,57 +27444,57 @@ 1 2 -45920 +45925 2 3 -72595 +72602 3 4 -57782 +57788 4 5 -38449 +38453 5 6 -28934 +28937 6 8 -40784 +40789 8 9 -19862 +19864 9 12 -34567 +34571 12 18 -36892 +36896 18 25 -33886 +33889 25 128 -33508 +33511 129 @@ -27737,57 +27515,57 @@ 1 2 -45261 +45265 2 3 -72660 +72667 3 4 -56711 +56717 4 5 -38644 +38647 5 6 -28707 +28710 6 8 -40601 +40605 8 9 -19613 +19615 9 12 -35281 +35284 12 18 -36816 +36820 18 25 -34135 +34138 25 108 -33497 +33500 108 @@ -27950,7 +27728,7 @@ 1 2 -4451330 +4451786 @@ -27966,12 +27744,12 @@ 1 2 -4424374 +4424828 2 14 -26955 +26958 @@ -27981,15 +27759,15 @@ enclosingfunction -153613 +153629 child -153613 +153629 parent -85527 +85535 @@ -28003,7 +27781,7 @@ 1 2 -153613 +153629 @@ -28019,22 +27797,22 @@ 1 2 -41606 +41610 2 3 -31410 +31413 3 4 -6314 +6315 4 49 -6195 +6196 @@ -28044,15 +27822,15 @@ derivations -212282 +212304 derivation -212282 +212304 sub -200497 +200517 index @@ -28060,11 +27838,11 @@ super -151408 +151423 location -78920 +78928 @@ -28078,7 +27856,7 @@ 1 2 -212282 +212304 @@ -28094,7 +27872,7 @@ 1 2 -212282 +212304 @@ -28110,7 +27888,7 @@ 1 2 -212282 +212304 @@ -28126,7 +27904,7 @@ 1 2 -212282 +212304 @@ -28142,12 +27920,12 @@ 1 2 -190387 +190406 2 7 -10109 +10110 @@ -28163,12 +27941,12 @@ 1 2 -191738 +191758 2 7 -8758 +8759 @@ -28184,12 +27962,12 @@ 1 2 -190387 +190406 2 7 -10109 +10110 @@ -28205,12 +27983,12 @@ 1 2 -191771 +191790 2 7 -8725 +8726 @@ -28390,12 +28168,12 @@ 1 2 -142174 +142188 2 454 -9233 +9234 @@ -28411,12 +28189,12 @@ 1 2 -142174 +142188 2 454 -9233 +9234 @@ -28432,7 +28210,7 @@ 1 2 -150878 +150893 2 @@ -28453,12 +28231,12 @@ 1 2 -146207 +146222 2 441 -5200 +5201 @@ -28474,12 +28252,12 @@ 1 2 -62712 +62719 2 3 -6952 +6953 3 @@ -28505,22 +28283,22 @@ 1 2 -63469 +63476 2 3 -6292 +6293 3 9 -6260 +6261 9 422 -2897 +2898 @@ -28536,7 +28314,7 @@ 1 2 -78866 +78874 2 @@ -28557,7 +28335,7 @@ 1 2 -65221 +65227 2 @@ -28582,11 +28360,11 @@ derspecifiers -214726 +214748 der_id -212271 +212293 spec_id @@ -28604,7 +28382,7 @@ 1 2 -209817 +209838 2 @@ -28650,11 +28428,11 @@ direct_base_offsets -141676 +141691 der_id -141676 +141691 offset @@ -28672,7 +28450,7 @@ 1 2 -141676 +141691 @@ -28758,11 +28536,11 @@ virtual_base_offsets -5330 +5331 sub -2540 +2541 super @@ -29064,19 +28842,19 @@ frienddecls -88104 +88173 id -88104 +88173 type_id -9166 +9167 decl_id -11688 +11689 location @@ -29094,7 +28872,7 @@ 1 2 -88104 +88173 @@ -29110,7 +28888,7 @@ 1 2 -88104 +88173 @@ -29126,7 +28904,7 @@ 1 2 -88104 +88173 @@ -29142,12 +28920,12 @@ 1 2 -4995 +4994 2 3 -1714 +1715 3 @@ -29157,17 +28935,17 @@ 4 10 -719 +721 10 58 -742 +739 58 333 -467 +469 @@ -29183,17 +28961,17 @@ 1 2 -5122 +5121 2 3 -1672 +1673 3 5 -841 +842 5 @@ -29224,7 +29002,7 @@ 1 2 -8233 +8234 2 @@ -29250,7 +29028,7 @@ 1 2 -9174 +9175 2 @@ -29281,7 +29059,7 @@ 1 2 -9579 +9580 2 @@ -29290,13 +29068,13 @@ 4 -49 -881 +48 +877 -49 +48 669 -331 +335 @@ -29312,7 +29090,7 @@ 1 2 -10711 +10712 2 @@ -29347,7 +29125,7 @@ 3 -82029 +82098 61 @@ -29368,7 +29146,7 @@ 2 -6626 +6627 175 @@ -29394,7 +29172,7 @@ 3 -7679 +7680 29 @@ -29405,19 +29183,19 @@ comments -1452211 +1452360 id -1452211 +1452360 contents -738204 +738280 location -1452211 +1452360 @@ -29431,7 +29209,7 @@ 1 2 -1452211 +1452360 @@ -29447,7 +29225,7 @@ 1 2 -1452211 +1452360 @@ -29463,17 +29241,17 @@ 1 2 -632750 +632815 2 3 -65729 +65736 3 10112 -39725 +39729 @@ -29489,17 +29267,17 @@ 1 2 -632750 +632815 2 3 -65729 +65736 3 10112 -39725 +39729 @@ -29515,7 +29293,7 @@ 1 2 -1452211 +1452360 @@ -29531,7 +29309,7 @@ 1 2 -1452211 +1452360 @@ -29541,15 +29319,15 @@ commentbinding -678022 +678026 id -577498 +577524 element -653229 +653231 @@ -29563,17 +29341,17 @@ 1 2 -511292 +511323 2 3 -49694 +49699 3 67 -16510 +16501 @@ -29594,7 +29372,7 @@ 2 3 -24793 +24795 @@ -29604,15 +29382,15 @@ exprconv -8101935 +8102766 converted -8101578 +8102408 conversion -8101935 +8102766 @@ -29626,7 +29404,7 @@ 1 2 -8101222 +8102052 2 @@ -29647,7 +29425,7 @@ 1 2 -8101935 +8102766 @@ -29657,26 +29435,26 @@ compgenerated -7234879 +7235621 id -7234879 +7235621 namespaces -7676 +7677 id -7676 +7677 name -4335 +4336 @@ -29690,7 +29468,7 @@ 1 2 -7676 +7677 @@ -29726,15 +29504,15 @@ namespacembrs -1122082 +1122197 parentid -7211 +7212 memberid -1122082 +1122197 @@ -29824,7 +29602,7 @@ 1 2 -1122082 +1122197 @@ -29834,11 +29612,11 @@ exprparents -16457650 +16459337 expr_id -16456651 +16458338 child_index @@ -29846,7 +29624,7 @@ parent_id -11521206 +11522387 @@ -29860,7 +29638,7 @@ 1 2 -16455857 +16457543 2 @@ -29881,7 +29659,7 @@ 1 2 -16456615 +16458302 2 @@ -30054,17 +29832,17 @@ 1 2 -7490705 +7491473 2 3 -3531965 +3532327 3 403 -498535 +498586 @@ -30080,17 +29858,17 @@ 1 2 -7490705 +7491473 2 3 -3531797 +3532159 3 403 -498703 +498755 @@ -30100,22 +29878,22 @@ expr_isload -5336661 +5337208 expr_id -5336661 +5337208 conversionkinds -5283699 +5284241 expr_id -5283699 +5284241 kind @@ -30133,7 +29911,7 @@ 1 2 -5283699 +5284241 @@ -30184,11 +29962,11 @@ iscall -2866665 +2866959 caller -2866665 +2866959 kind @@ -30206,7 +29984,7 @@ 1 2 -2866665 +2866959 @@ -30242,11 +30020,11 @@ numtemplatearguments -167507 +167525 expr_id -167507 +167525 num @@ -30264,7 +30042,7 @@ 1 2 -167507 +167525 @@ -30353,23 +30131,23 @@ namequalifiers -938842 +938938 id -938842 +938938 qualifiableelement -938842 +938938 qualifyingelement -77266 +77274 location -195793 +195813 @@ -30383,7 +30161,7 @@ 1 2 -938842 +938938 @@ -30399,7 +30177,7 @@ 1 2 -938842 +938938 @@ -30415,7 +30193,7 @@ 1 2 -938842 +938938 @@ -30431,7 +30209,7 @@ 1 2 -938842 +938938 @@ -30447,7 +30225,7 @@ 1 2 -938842 +938938 @@ -30463,7 +30241,7 @@ 1 2 -938842 +938938 @@ -30479,12 +30257,12 @@ 1 2 -42385 +42389 2 3 -13288 +13289 3 @@ -30494,7 +30272,7 @@ 4 6 -6703 +6704 6 @@ -30520,12 +30298,12 @@ 1 2 -42385 +42389 2 3 -13288 +13289 3 @@ -30535,7 +30313,7 @@ 4 6 -6703 +6704 6 @@ -30561,12 +30339,12 @@ 1 2 -50462 +50467 2 3 -12099 +12100 3 @@ -30576,7 +30354,7 @@ 5 13 -5795 +5796 13 @@ -30597,32 +30375,32 @@ 1 2 -84197 +84205 2 3 -34340 +34344 3 4 -32826 +32830 4 6 -16770 +16771 6 11 -16121 +16123 11 1537 -11536 +11538 @@ -30638,32 +30416,32 @@ 1 2 -84197 +84205 2 3 -34340 +34344 3 4 -32826 +32830 4 6 -16770 +16771 6 11 -16121 +16123 11 1537 -11536 +11538 @@ -30679,22 +30457,22 @@ 1 2 -152954 +152969 2 3 -18543 +18545 3 4 -16251 +16252 4 312 -8044 +8045 @@ -30704,15 +30482,15 @@ varbind -6733144 +6733835 expr -6733144 +6733835 var -1307660 +1307794 @@ -30726,7 +30504,7 @@ 1 2 -6733144 +6733835 @@ -30742,42 +30520,42 @@ 1 2 -424200 +424243 2 3 -257315 +257341 3 4 -163318 +163334 4 5 -99044 +99054 5 6 -125226 +125239 6 9 -116161 +116173 9 25 -99216 +99226 25 69786 -23176 +23179 @@ -30787,15 +30565,15 @@ funbind -2920165 +2920465 expr -2854749 +2855042 fun -945870 +945967 @@ -30809,12 +30587,12 @@ 1 2 -2790501 +2790787 2 4 -64248 +64254 @@ -30830,27 +30608,27 @@ 1 2 -620650 +620714 2 3 -147418 +147433 3 4 -56300 +56306 4 8 -72757 +72765 8 8325 -48742 +48747 @@ -30860,11 +30638,11 @@ expr_allocator -33994 +33998 expr -33994 +33998 func @@ -30886,7 +30664,7 @@ 1 2 -33994 +33998 @@ -30902,7 +30680,7 @@ 1 2 -33994 +33998 @@ -31006,11 +30784,11 @@ expr_deallocator -37065 +37069 expr -37065 +37069 func @@ -31032,7 +30810,7 @@ 1 2 -37065 +37069 @@ -31048,7 +30826,7 @@ 1 2 -37065 +37069 @@ -31167,19 +30945,19 @@ values -10236794 +10237844 id -10236794 +10237844 str -851497 +851584 text -994455 +994557 @@ -31193,7 +30971,7 @@ 1 2 -10236794 +10237844 @@ -31209,7 +30987,7 @@ 1 2 -10236794 +10237844 @@ -31225,17 +31003,17 @@ 1 2 -715723 +715797 2 3 -73308 +73315 3 3384226 -62465 +62471 @@ -31251,12 +31029,12 @@ 1 2 -827010 +827094 2 23650 -24487 +24489 @@ -31272,22 +31050,22 @@ 1 2 -738480 +738556 2 3 -125557 +125570 3 6 -78628 +78636 6 1512711 -51789 +51794 @@ -31303,12 +31081,12 @@ 1 2 -966478 +966577 2 5856 -27976 +27979 @@ -31318,15 +31096,15 @@ valuebind -11049974 +11051106 val -10230278 +10231326 expr -11049974 +11051106 @@ -31340,12 +31118,12 @@ 1 2 -9411675 +9412640 2 3 -817517 +817601 3 @@ -31366,7 +31144,7 @@ 1 2 -11049974 +11051106 @@ -31376,11 +31154,11 @@ fieldoffsets -275081 +275110 id -275081 +275110 byteoffset @@ -31402,7 +31180,7 @@ 1 2 -275081 +275110 @@ -31418,7 +31196,7 @@ 1 2 -275081 +275110 @@ -31572,11 +31350,11 @@ bitfield -16281 +16283 id -16281 +16283 bits @@ -31598,7 +31376,7 @@ 1 2 -16281 +16283 @@ -31614,7 +31392,7 @@ 1 2 -16281 +16283 @@ -31778,23 +31556,23 @@ initialisers -1574731 +1575751 init -1574731 +1575751 var -623051 +623081 expr -1574731 +1575751 location -427350 +427351 @@ -31808,7 +31586,7 @@ 1 2 -1574731 +1575751 @@ -31824,7 +31602,7 @@ 1 2 -1574731 +1575751 @@ -31840,7 +31618,7 @@ 1 2 -1574731 +1575751 @@ -31856,17 +31634,17 @@ 1 2 -540016 +540041 2 6 -47047 +47030 6 -7689 -35988 +7698 +36010 @@ -31882,17 +31660,17 @@ 1 2 -540016 +540041 2 6 -47047 +47030 6 -7689 -35988 +7698 +36010 @@ -31908,7 +31686,7 @@ 1 2 -622233 +622263 2 @@ -31929,7 +31707,7 @@ 1 2 -1574731 +1575751 @@ -31945,7 +31723,7 @@ 1 2 -1574731 +1575751 @@ -31961,7 +31739,7 @@ 1 2 -1574731 +1575751 @@ -31982,12 +31760,12 @@ 2 6 -33647 +33634 6 -545290 -18838 +545820 +18852 @@ -32003,11 +31781,11 @@ 1 2 -405292 +405293 2 -58774 +58779 22058 @@ -32029,12 +31807,12 @@ 2 6 -33647 +33634 6 -545290 -18838 +545820 +18852 @@ -32044,15 +31822,15 @@ expr_ancestor -154464 +154479 exp -154131 +154141 ancestor -85937 +85950 @@ -32066,12 +31844,12 @@ 1 2 -153957 +153962 2 34 -174 +179 @@ -32087,12 +31865,12 @@ 1 2 -52524 +52535 2 3 -19526 +19528 3 @@ -32117,11 +31895,11 @@ exprs -22915784 +22918133 id -22915784 +22918133 kind @@ -32129,7 +31907,7 @@ location -21065613 +11214099 @@ -32143,7 +31921,7 @@ 1 2 -22915784 +22918133 @@ -32159,7 +31937,7 @@ 1 2 -22915784 +22918133 @@ -32255,72 +32033,72 @@ 1 -5 +3 9 -7 -59 +3 +16 9 -93 -197 +20 +96 9 -234 -466 +103 +216 9 -511 -704 +234 +420 9 -759 -1608 +467 +1084 9 -1826 -3479 +1412 +2623 9 -4546 -7284 +2855 +4876 9 -10995 -16080 +5065 +11556 9 -16465 -28204 +12240 +16164 9 -31256 -58772 +18187 +35051 9 -59070 -368790 +37139 +363282 9 -561588 -3494324 +389796 +2941429 9 -3976014 -3976015 +3053411 +3053412 1 @@ -32337,12 +32115,27 @@ 1 2 -20761606 +7487173 2 +3 +1765332 + + +3 +4 +1107217 + + +4 +43 +844600 + + +43 148794 -304007 +9774 @@ -32358,12 +32151,22 @@ 1 2 -20824583 +7569304 2 -20 -241029 +3 +1781815 + + +3 +4 +1582415 + + +4 +32 +280564 @@ -32373,15 +32176,15 @@ expr_types -23145031 +23147404 id -22915784 +22918133 typeid -1638608 +1638776 value_category @@ -32399,12 +32202,12 @@ 1 2 -22688752 +22691078 2 4 -227031 +227054 @@ -32420,7 +32223,7 @@ 1 2 -22915784 +22918133 @@ -32436,42 +32239,42 @@ 1 2 -668463 +668532 2 3 -299594 +299624 3 4 -125998 +126011 4 5 -110342 +110353 5 8 -145990 +145984 8 15 -131588 +131602 15 62 -123198 +123243 62 230270 -33432 +33425 @@ -32487,17 +32290,17 @@ 1 2 -1425450 +1425607 2 3 -205189 +205199 3 4 -7968 +7969 @@ -32542,8 +32345,8 @@ 10 -36935 -36936 +36934 +36935 10 @@ -32559,15 +32362,15 @@ new_allocated_type -36124 +36128 expr -36124 +36128 type_id -22327 +22330 @@ -32581,7 +32384,7 @@ 1 2 -36124 +36128 @@ -32597,12 +32400,12 @@ 1 2 -14942 +14944 2 3 -5665 +5666 3 @@ -32690,15 +32493,15 @@ aggregate_field_init -11054 +11055 aggregate -11054 +11055 initializer -11054 +11055 field @@ -32716,7 +32519,7 @@ 1 2 -11054 +11055 @@ -32732,7 +32535,7 @@ 1 2 -11054 +11055 @@ -32748,7 +32551,7 @@ 1 2 -11054 +11055 @@ -32764,7 +32567,7 @@ 1 2 -11054 +11055 @@ -32846,15 +32649,15 @@ aggregate_array_init -26944 +26947 aggregate -26944 +26947 initializer -26944 +26947 element_index @@ -32872,7 +32675,7 @@ 1 2 -26944 +26947 @@ -32888,7 +32691,7 @@ 1 2 -26944 +26947 @@ -32904,7 +32707,7 @@ 1 2 -26944 +26947 @@ -32920,7 +32723,7 @@ 1 2 -26944 +26947 @@ -33022,15 +32825,15 @@ condition_decl_bind -12283 +12284 expr -12283 +12284 decl -12283 +12284 @@ -33044,7 +32847,7 @@ 1 2 -12283 +12284 @@ -33060,7 +32863,7 @@ 1 2 -12283 +12284 @@ -33201,11 +33004,11 @@ sizeof_bind -209079 +209101 expr -209079 +209101 type_id @@ -33223,7 +33026,7 @@ 1 2 -209079 +209101 @@ -34095,11 +33898,11 @@ stmts -5729686 +5730273 id -5729686 +5730273 kind @@ -34107,7 +33910,7 @@ location -5648646 +1258698 @@ -34121,7 +33924,7 @@ 1 2 -5729686 +5730273 @@ -34137,7 +33940,7 @@ 1 2 -5729686 +5730273 @@ -34262,93 +34065,93 @@ 10 -25 -26 +21 +22 10 -452 -453 +135 +136 10 -621 -622 +150 +151 10 -856 -857 +167 +168 10 -1785 -1786 +238 +239 10 -1970 -1971 +331 +332 10 -2507 -2508 +722 +723 10 -2656 -2657 +920 +921 10 -2801 -2802 +1164 +1165 10 -3377 -3378 +1269 +1270 10 -3775 -3776 +1284 +1285 10 -4428 -4429 +2345 +2346 10 -6227 -6228 +3245 +3246 10 -40098 -40099 +11403 +11404 10 -63840 -63841 +12023 +12024 10 -116834 -116835 +25670 +25671 10 -124824 -124825 +26075 +26076 10 -147312 -147313 +32549 +32550 10 @@ -34365,12 +34168,32 @@ 1 2 -5627313 +653901 2 +3 +248086 + + +3 +4 +117739 + + +4 +6 +64568 + + +6 +11 +102502 + + +11 5521 -21333 +71899 @@ -34386,12 +34209,12 @@ 1 2 -5627313 +1234367 2 -4 -21333 +9 +24330 @@ -34497,15 +34320,15 @@ if_then -661372 +661440 if_stmt -661372 +661440 then_id -661372 +661440 @@ -34519,7 +34342,7 @@ 1 2 -661372 +661440 @@ -34535,7 +34358,7 @@ 1 2 -661372 +661440 @@ -34545,15 +34368,15 @@ if_else -193032 +193052 if_stmt -193032 +193052 else_id -193032 +193052 @@ -34567,7 +34390,7 @@ 1 2 -193032 +193052 @@ -34583,7 +34406,7 @@ 1 2 -193032 +193052 @@ -34593,15 +34416,15 @@ while_body -40817 +40821 while_stmt -40817 +40821 body_id -40817 +40821 @@ -34615,7 +34438,7 @@ 1 2 -40817 +40821 @@ -34631,7 +34454,7 @@ 1 2 -40817 +40821 @@ -34641,15 +34464,15 @@ do_body -220160 +220163 do_stmt -220160 +220163 body_id -220160 +220163 @@ -34663,7 +34486,7 @@ 1 2 -220160 +220163 @@ -34679,7 +34502,7 @@ 1 2 -220160 +220163 @@ -34689,11 +34512,11 @@ switch_case -390492 +390532 switch_stmt -76506 +76514 index @@ -34701,7 +34524,7 @@ case_id -390492 +390532 @@ -34715,12 +34538,12 @@ 1 5 -5907 +5908 5 6 -67571 +67578 6 @@ -34741,12 +34564,12 @@ 1 5 -5907 +5908 5 6 -67571 +67578 6 @@ -34879,7 +34702,7 @@ 1 2 -390492 +390532 @@ -34895,7 +34718,7 @@ 1 2 -390492 +390532 @@ -34905,15 +34728,15 @@ switch_body -76506 +76514 switch_stmt -76506 +76514 body_id -76506 +76514 @@ -34927,7 +34750,7 @@ 1 2 -76506 +76514 @@ -34943,7 +34766,7 @@ 1 2 -76506 +76514 @@ -35001,15 +34824,15 @@ for_condition -40832 +40834 for_stmt -40832 +40834 condition_id -40832 +40834 @@ -35023,7 +34846,7 @@ 1 2 -40832 +40834 @@ -35039,7 +34862,7 @@ 1 2 -40832 +40834 @@ -35049,15 +34872,15 @@ for_update -39998 +40000 for_stmt -39998 +40000 update_id -39998 +40000 @@ -35071,7 +34894,7 @@ 1 2 -39998 +40000 @@ -35087,7 +34910,7 @@ 1 2 -39998 +40000 @@ -35097,15 +34920,15 @@ for_body -42107 +42109 for_stmt -42107 +42109 body_id -42107 +42109 @@ -35119,7 +34942,7 @@ 1 2 -42107 +42109 @@ -35135,7 +34958,7 @@ 1 2 -42107 +42109 @@ -35145,11 +34968,11 @@ stmtparents -5440576 +5441133 id -5440576 +5441133 index @@ -35157,7 +34980,7 @@ parent -1964292 +1964493 @@ -35171,7 +34994,7 @@ 1 2 -5440576 +5441133 @@ -35187,7 +35010,7 @@ 1 2 -5440576 +5441133 @@ -35325,32 +35148,32 @@ 1 2 -990069 +990171 2 3 -486055 +486105 3 4 -164812 +164829 4 7 -154095 +154111 7 17 -148829 +148845 17 464 -20429 +20431 @@ -35366,32 +35189,32 @@ 1 2 -990069 +990171 2 3 -486055 +486105 3 4 -164812 +164829 4 7 -154095 +154111 7 17 -148829 +148845 17 464 -20429 +20431 @@ -35401,26 +35224,26 @@ ishandler -21300 +21302 block -21300 +21302 successors -21708989 +21711214 from -20317024 +20319106 to -20314962 +20317045 @@ -35434,12 +35257,12 @@ 1 2 -19165457 +19167421 2 156 -1151566 +1151685 @@ -35455,12 +35278,12 @@ 1 2 -19493015 +19495013 2 349 -821947 +822032 @@ -35470,15 +35293,15 @@ truecond -1251979 +1252107 from -1251979 +1252107 to -1211693 +1211818 @@ -35492,7 +35315,7 @@ 1 2 -1251979 +1252107 @@ -35508,12 +35331,12 @@ 1 2 -1178691 +1178812 2 23 -33001 +33005 @@ -35523,15 +35346,15 @@ falsecond -1251979 +1252107 from -1251979 +1252107 to -1031223 +1031329 @@ -35545,7 +35368,7 @@ 1 2 -1251979 +1252107 @@ -35561,17 +35384,17 @@ 1 2 -882058 +882148 2 3 -110157 +110169 3 22 -39007 +39011 @@ -35581,11 +35404,11 @@ stmt_decl_bind -706223 +706295 stmt -644204 +644270 num @@ -35593,7 +35416,7 @@ decl -680265 +680335 @@ -35607,12 +35430,12 @@ 1 2 -599785 +599846 2 270 -44419 +44424 @@ -35628,12 +35451,12 @@ 1 2 -599785 +599846 2 15 -44419 +44424 @@ -35706,12 +35529,12 @@ 1 2 -672395 +672464 2 81 -7869 +7870 @@ -35727,7 +35550,7 @@ 1 2 -680204 +680274 2 @@ -35742,11 +35565,11 @@ stmt_decl_entry_bind -706223 +706295 stmt -644204 +644270 num @@ -35754,7 +35577,7 @@ decl_entry -683641 +683711 @@ -35768,12 +35591,12 @@ 1 2 -599785 +599846 2 270 -44419 +44424 @@ -35789,12 +35612,12 @@ 1 2 -599785 +599846 2 15 -44419 +44424 @@ -35867,12 +35690,12 @@ 1 2 -676106 +676175 2 17 -7534 +7535 @@ -35888,7 +35711,7 @@ 1 2 -683566 +683636 2 @@ -35903,15 +35726,15 @@ blockscope -1621708 +1621875 block -1621697 +1621864 enclosing -1395434 +1395577 @@ -35925,7 +35748,7 @@ 1 2 -1621687 +1621853 2 @@ -35946,12 +35769,12 @@ 1 2 -1297494 +1297627 2 692 -97940 +97950 @@ -35961,19 +35784,19 @@ jumpinfo -511797 +511850 id -511797 +511850 str -8948 +8949 target -119526 +119538 @@ -35987,7 +35810,7 @@ 1 2 -511797 +511850 @@ -36003,7 +35826,7 @@ 1 2 -511797 +511850 @@ -36065,7 +35888,7 @@ 1 2 -7116 +7117 2 @@ -36101,12 +35924,12 @@ 2 3 -30183 +30186 3 4 -10617 +10618 4 @@ -36116,12 +35939,12 @@ 5 6 -55227 +55233 6 7 -15293 +15295 7 @@ -36142,7 +35965,7 @@ 1 2 -119526 +119538 @@ -36152,11 +35975,11 @@ preprocdirects -1178632 +1178753 id -1178632 +1178753 kind @@ -36164,7 +35987,7 @@ location -1172328 +1172448 @@ -36178,7 +36001,7 @@ 1 2 -1178632 +1178753 @@ -36194,7 +36017,7 @@ 1 2 -1178632 +1178753 @@ -36372,7 +36195,7 @@ 1 2 -1172004 +1172124 2 @@ -36393,7 +36216,7 @@ 1 2 -1172328 +1172448 @@ -36403,15 +36226,15 @@ preprocpair -319986 +320019 begin -253900 +253926 elseelifend -319986 +320019 @@ -36425,12 +36248,12 @@ 1 2 -201080 +201101 2 3 -47121 +47125 3 @@ -36451,7 +36274,7 @@ 1 2 -319986 +320019 @@ -36461,41 +36284,41 @@ preproctrue -142736 +142751 branch -142736 +142751 preprocfalse -103486 +103497 branch -103486 +103497 preproctext -874961 +875051 id -874961 +875051 head -433723 +433768 body -170784 +170801 @@ -36509,7 +36332,7 @@ 1 2 -874961 +875051 @@ -36525,7 +36348,7 @@ 1 2 -874961 +875051 @@ -36541,17 +36364,17 @@ 1 2 -332583 +332617 2 3 -67232 +67239 3 45 -32545 +32549 45 @@ -36572,12 +36395,12 @@ 1 2 -417948 +417990 2 40 -15775 +15777 @@ -36593,12 +36416,12 @@ 1 2 -160717 +160734 2 58050 -10066 +10067 @@ -36614,12 +36437,12 @@ 1 2 -161528 +161545 2 19352 -9255 +9256 @@ -36629,15 +36452,15 @@ includes -274638 +274666 id -274638 +274666 included -50278 +50283 @@ -36651,7 +36474,7 @@ 1 2 -274638 +274666 @@ -36667,12 +36490,12 @@ 1 2 -24339 +24341 2 3 -8347 +8348 3 @@ -36692,12 +36515,12 @@ 11 32 -3816 +3817 32 684 -1275 +1276 @@ -36755,15 +36578,15 @@ link_parent -12587521 +12586973 element -4526520 +4526984 link_target -637 +638 @@ -36777,27 +36600,27 @@ 1 2 -2738796 +2739109 2 3 -989910 +990011 3 5 -407319 +407361 5 36 -346325 +346361 36 -60 -44169 +45 +44141 @@ -36811,62 +36634,62 @@ 12 -5 -7 +2 +4 54 -7 -9 +4 +6 43 -9 -25 +6 +22 54 -50 -977 +47 +974 54 -4267 -6132 +4264 +6129 54 -6208 -8494 +6205 +8491 54 -8552 -14647 +8549 +14644 54 -16356 -19486 +16353 +19483 54 -19585 -21586 +19582 +21583 54 -22124 -27375 +22121 +27372 54 -27427 -38409 +27424 +38406 54 -39374 +39371 326548 54 diff --git a/cpp/ql/test/examples/expressions/PrintAST.expected b/cpp/ql/test/examples/expressions/PrintAST.expected index 065488c78ce8..d86f698e2a23 100644 --- a/cpp/ql/test/examples/expressions/PrintAST.expected +++ b/cpp/ql/test/examples/expressions/PrintAST.expected @@ -1,7 +1,11 @@ -#-----| __va_list_tag::operator=() -> __va_list_tag & +#-----| __va_list_tag::operator=(__va_list_tag &&) -> __va_list_tag & #-----| params: -#-----| __va_list_tag::operator=() -> __va_list_tag & +#-----| 0: p#0 +#-----| Type = __va_list_tag && +#-----| __va_list_tag::operator=(const __va_list_tag &) -> __va_list_tag & #-----| params: +#-----| 0: p#0 +#-----| Type = const __va_list_tag & #-----| operator delete(void *) -> void #-----| params: #-----| 0: p#0 diff --git a/cpp/ql/test/header-variant-tests/deduplication/variables.expected b/cpp/ql/test/header-variant-tests/deduplication/variables.expected index 05233432cdbd..3db8c3c168cf 100644 --- a/cpp/ql/test/header-variant-tests/deduplication/variables.expected +++ b/cpp/ql/test/header-variant-tests/deduplication/variables.expected @@ -4,9 +4,11 @@ | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | C && | Variable | | | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | C && | Variable | | | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | D && | Variable | | +| file://:0:0:0:0 | p#0 | file://:0:0:0:0 | __va_list_tag && | Variable | | | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | const C & | Variable | | | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | const C & | Variable | | | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | const D & | Variable | | +| file://:0:0:0:0 | p#0 | file://:0:0:0:0 | const __va_list_tag & | Variable | | | file://:0:0:0:0 | reg_save_area | file://:0:0:0:0 | void * | MemberVariable | __va_list_tag | | foo.h:7:5:7:15 | foo_defined | file://:0:0:0:0 | int | MemberVariable | C | | foo.h:7:5:7:15 | foo_defined | file://:0:0:0:0 | int | Variable | | diff --git a/cpp/ql/test/header-variant-tests/functions-in-headers/one_x.expected b/cpp/ql/test/header-variant-tests/functions-in-headers/one_x.expected index 990c69a3946d..1039dac1595f 100644 --- a/cpp/ql/test/header-variant-tests/functions-in-headers/one_x.expected +++ b/cpp/ql/test/header-variant-tests/functions-in-headers/one_x.expected @@ -2,4 +2,6 @@ | file://:0:0:0:0 | fp_offset | fp_offset | false | | file://:0:0:0:0 | gp_offset | gp_offset | false | | file://:0:0:0:0 | overflow_arg_area | overflow_arg_area | false | +| file://:0:0:0:0 | p#0 | p#0 | false | +| file://:0:0:0:0 | p#0 | p#0 | false | | file://:0:0:0:0 | reg_save_area | reg_save_area | false | diff --git a/cpp/ql/test/library-tests/CPP-205/elements.expected b/cpp/ql/test/library-tests/CPP-205/elements.expected index 5988280f9a91..fbfc97a4e46c 100644 --- a/cpp/ql/test/library-tests/CPP-205/elements.expected +++ b/cpp/ql/test/library-tests/CPP-205/elements.expected @@ -1,30 +1,34 @@ -| CPP-205.cpp:0:0:0:0 | CPP-205.cpp | -| CPP-205.cpp:1:20:1:20 | T | -| CPP-205.cpp:1:20:1:20 | definition of T | -| CPP-205.cpp:2:5:2:6 | definition of fn | -| CPP-205.cpp:2:5:2:6 | fn | -| CPP-205.cpp:2:5:2:6 | fn | -| CPP-205.cpp:2:10:2:12 | definition of out | -| CPP-205.cpp:2:10:2:12 | out | -| CPP-205.cpp:2:10:2:12 | out | -| CPP-205.cpp:2:15:5:1 | { ... } | -| CPP-205.cpp:2:15:5:1 | { ... } | -| CPP-205.cpp:3:3:3:33 | declaration | -| CPP-205.cpp:3:3:3:33 | declaration | -| CPP-205.cpp:3:15:3:15 | declaration of y | -| CPP-205.cpp:3:15:3:15 | declaration of y | -| CPP-205.cpp:3:15:3:15 | y | -| CPP-205.cpp:3:15:3:15 | y | -| CPP-205.cpp:3:17:3:31 | 5 | -| CPP-205.cpp:4:3:4:11 | return ... | -| CPP-205.cpp:4:3:4:11 | return ... | -| CPP-205.cpp:4:10:4:10 | 0 | -| CPP-205.cpp:4:10:4:10 | 0 | -| CPP-205.cpp:7:5:7:8 | definition of main | -| CPP-205.cpp:7:5:7:8 | main | -| CPP-205.cpp:7:12:9:1 | { ... } | -| CPP-205.cpp:8:3:8:15 | return ... | -| CPP-205.cpp:8:10:8:11 | call to fn | -| CPP-205.cpp:8:13:8:13 | 0 | -| file://:0:0:0:0 | operator= | -| file://:0:0:0:0 | operator= | +| CPP-205.cpp:0:0:0:0 | CPP-205.cpp | | +| CPP-205.cpp:1:20:1:20 | T | | +| CPP-205.cpp:1:20:1:20 | definition of T | | +| CPP-205.cpp:2:5:2:5 | definition of fn | function declaration entry for fn(int) -> int | +| CPP-205.cpp:2:5:2:5 | fn | function fn(int) -> int | +| CPP-205.cpp:2:5:2:6 | definition of fn | function declaration entry for fn(T) -> int | +| CPP-205.cpp:2:5:2:6 | fn | function fn(T) -> int | +| CPP-205.cpp:2:10:2:12 | definition of out | parameter declaration entry for fn(T) -> int | +| CPP-205.cpp:2:10:2:12 | definition of out | parameter declaration entry for fn(int) -> int | +| CPP-205.cpp:2:10:2:12 | out | parameter for fn(T) -> int | +| CPP-205.cpp:2:10:2:12 | out | parameter for fn(int) -> int | +| CPP-205.cpp:2:15:5:1 | { ... } | | +| CPP-205.cpp:2:15:5:1 | { ... } | | +| CPP-205.cpp:3:3:3:33 | declaration | | +| CPP-205.cpp:3:3:3:33 | declaration | | +| CPP-205.cpp:3:15:3:15 | declaration of y | | +| CPP-205.cpp:3:15:3:15 | y | | +| CPP-205.cpp:3:17:3:31 | 5 | | +| CPP-205.cpp:4:3:4:11 | return ... | | +| CPP-205.cpp:4:3:4:11 | return ... | | +| CPP-205.cpp:4:10:4:10 | 0 | | +| CPP-205.cpp:4:10:4:10 | 0 | | +| CPP-205.cpp:7:5:7:8 | definition of main | function declaration entry for main() -> int | +| CPP-205.cpp:7:5:7:8 | main | function main() -> int | +| CPP-205.cpp:7:12:9:1 | { ... } | | +| CPP-205.cpp:8:3:8:15 | return ... | | +| CPP-205.cpp:8:10:8:11 | call to fn | | +| CPP-205.cpp:8:13:8:13 | 0 | | +| file://:0:0:0:0 | __va_list_tag | | +| file://:0:0:0:0 | operator= | function __va_list_tag::operator=(__va_list_tag &&) -> __va_list_tag & | +| file://:0:0:0:0 | operator= | function __va_list_tag::operator=(const __va_list_tag &) -> __va_list_tag & | +| file://:0:0:0:0 | p#0 | parameter for __va_list_tag::operator=(__va_list_tag &&) -> __va_list_tag & | +| file://:0:0:0:0 | p#0 | parameter for __va_list_tag::operator=(const __va_list_tag &) -> __va_list_tag & | +| file://:0:0:0:0 | y | | diff --git a/cpp/ql/test/library-tests/CPP-205/elements.ql b/cpp/ql/test/library-tests/CPP-205/elements.ql index 36659f08c985..eb21ae777938 100644 --- a/cpp/ql/test/library-tests/CPP-205/elements.ql +++ b/cpp/ql/test/library-tests/CPP-205/elements.ql @@ -1,6 +1,16 @@ import cpp +string describe(Element e) { + result = "function " + e.(Function).getFullSignature() + or + result = "function declaration entry for " + e.(FunctionDeclarationEntry).getFunction().getFullSignature() + or + result = "parameter for " + e.(Parameter).getFunction().getFullSignature() + or + result = "parameter declaration entry for " + e.(ParameterDeclarationEntry).getFunctionDeclarationEntry().getFunction().getFullSignature() +} + from Element e where not e.getLocation() instanceof UnknownLocation and not e instanceof Folder -select e +select e, concat(describe(e), ", ") diff --git a/cpp/ql/test/library-tests/atomic/variables.expected b/cpp/ql/test/library-tests/atomic/variables.expected index e5303709924c..bd56dc05e0f9 100644 --- a/cpp/ql/test/library-tests/atomic/variables.expected +++ b/cpp/ql/test/library-tests/atomic/variables.expected @@ -13,7 +13,9 @@ | k | _Atomic(int) *_Atomic | atomic {pointer to {atomic {int}}} | | m | int | int | | overflow_arg_area | void * | pointer to {void} | +| p#0 | __va_list_tag && | rvalue reference to {struct __va_list_tag} | | p#0 | atomic_box && | rvalue reference to {struct atomic_box} | +| p#0 | const __va_list_tag & | reference to {const {struct __va_list_tag}} | | p#0 | const atomic_box & | reference to {const {struct atomic_box}} | | reg_save_area | void * | pointer to {void} | | value | _Atomic(T) | atomic {T} | diff --git a/cpp/ql/test/library-tests/blocks/cpp/blocks.expected b/cpp/ql/test/library-tests/blocks/cpp/blocks.expected index 5a4830a470f1..1716b2ef93c2 100644 --- a/cpp/ql/test/library-tests/blocks/cpp/blocks.expected +++ b/cpp/ql/test/library-tests/blocks/cpp/blocks.expected @@ -26,5 +26,7 @@ | file://:0:0:0:0 | gp_offset | file://:0:0:0:0 | unsigned int | unsigned int | | file://:0:0:0:0 | overflow_arg_area | file://:0:0:0:0 | void * | pointer to {void} | | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | Example && | rvalue reference to {struct Example} | +| file://:0:0:0:0 | p#0 | file://:0:0:0:0 | __va_list_tag && | rvalue reference to {struct __va_list_tag} | | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | const Example & | reference to {const {struct Example}} | +| file://:0:0:0:0 | p#0 | file://:0:0:0:0 | const __va_list_tag & | reference to {const {struct __va_list_tag}} | | file://:0:0:0:0 | reg_save_area | file://:0:0:0:0 | void * | pointer to {void} | diff --git a/cpp/ql/test/library-tests/blocks/deduplication/functions.expected b/cpp/ql/test/library-tests/blocks/deduplication/functions.expected index 9f4d90dd1f1f..7e55b4322aa4 100644 --- a/cpp/ql/test/library-tests/blocks/deduplication/functions.expected +++ b/cpp/ql/test/library-tests/blocks/deduplication/functions.expected @@ -1,3 +1,3 @@ | 1 | 1 | -| 2 | 1 | +| 2 | 2 | | 9 | 2 | diff --git a/cpp/ql/test/library-tests/builtins/functions_file/isbuiltin.expected b/cpp/ql/test/library-tests/builtins/functions_file/isbuiltin.expected index 00a331f6f44e..72e065f79a89 100644 --- a/cpp/ql/test/library-tests/builtins/functions_file/isbuiltin.expected +++ b/cpp/ql/test/library-tests/builtins/functions_file/isbuiltin.expected @@ -1,4 +1,4 @@ | file://:0:0:0:0 | __builtin_add_overflow | true | 0 | file://:0:0:0:0 | bool | -| file://:0:0:0:0 | __builtin_foobar | true | 0 | file://:0:0:0:0 | int | -| file://:0:0:0:0 | __builtin_malloc | true | 0 | file://:0:0:0:0 | float | +| file://:0:0:0:0 | __builtin_foobar | true | 1 | file://:0:0:0:0 | int | +| file://:0:0:0:0 | __builtin_malloc | true | 4 | file://:0:0:0:0 | float | | test.c:1:6:1:6 | f | false | 3 | file://:0:0:0:0 | long | diff --git a/cpp/ql/test/library-tests/c++_exceptions/graphable.expected b/cpp/ql/test/library-tests/c++_exceptions/graphable.expected index 18a1959c53a1..101825071458 100644 --- a/cpp/ql/test/library-tests/c++_exceptions/graphable.expected +++ b/cpp/ql/test/library-tests/c++_exceptions/graphable.expected @@ -1,384 +1,384 @@ -| C::C | false | 400 | 400 | C | -| C::C | false | 406 | 406 | C | -| C::operator= | false | 398 | 398 | operator= | -| C::~C | false | 403 | 403 | ~C | -| Error::Error | false | 71 | 71 | Error | -| Error::Error | false | 74 | 74 | return ... | -| Error::Error | false | 76 | 76 | { ... } | -| Error::Error | false | 95 | 95 | Error | -| Error::Error | true | 74 | 71 | | -| Error::Error | true | 76 | 74 | | -| Error::operator= | false | 93 | 93 | operator= | -| Error::~Error | false | 79 | 79 | ~Error | -| Error::~Error | false | 82 | 82 | return ... | -| Error::~Error | false | 84 | 84 | { ... } | -| Error::~Error | true | 82 | 79 | | -| Error::~Error | true | 84 | 82 | | -| __va_list_tag::operator= | false | 60 | 60 | operator= | -| __va_list_tag::operator= | false | 66 | 66 | operator= | -| f | false | 416 | 416 | f | -| f | false | 422 | 422 | call to C | -| f | false | 426 | 426 | 101 | -| f | false | 427 | 427 | initializer for c101 | -| f | false | 431 | 431 | declaration | -| f | false | 434 | 434 | call to C | -| f | false | 438 | 438 | 102 | -| f | false | 439 | 439 | initializer for c102 | -| f | false | 444 | 444 | call to C | -| f | false | 448 | 448 | 103 | -| f | false | 449 | 449 | initializer for c103 | -| f | false | 453 | 453 | declaration | -| f | false | 455 | 455 | b1 | -| f | false | 457 | 457 | (bool)... | -| f | false | 461 | 461 | 1 | -| f | false | 462 | 462 | throw ... | -| f | false | 464 | 464 | ExprStmt | -| f | false | 466 | 466 | { ... } | -| f | false | 468 | 468 | if (...) ... | -| f | false | 470 | 470 | declaration | -| f | false | 472 | 472 | { ... } | -| f | false | 478 | 478 | 1 | -| f | false | 480 | 480 | call to C | -| f | false | 484 | 484 | 104 | -| f | false | 485 | 485 | initializer for c104 | -| f | false | 489 | 489 | declaration | -| f | false | 491 | 491 | { ... } | -| f | false | 493 | 493 | __try { ... } __except( ... ) { ... } | -| f | false | 496 | 496 | call to C | -| f | false | 500 | 500 | 105 | -| f | false | 501 | 501 | initializer for c105 | -| f | false | 505 | 505 | declaration | -| f | false | 508 | 508 | call to C | -| f | false | 512 | 512 | 106 | -| f | false | 513 | 513 | initializer for c106 | -| f | false | 518 | 518 | call to C | -| f | false | 522 | 522 | 107 | -| f | false | 523 | 523 | initializer for c107 | -| f | false | 527 | 527 | declaration | -| f | false | 529 | 529 | b2 | -| f | false | 531 | 531 | (bool)... | -| f | false | 535 | 535 | 2 | -| f | false | 536 | 536 | throw ... | -| f | false | 538 | 538 | ExprStmt | -| f | false | 540 | 540 | { ... } | -| f | false | 542 | 542 | if (...) ... | +| C::C | false | 493 | 493 | C | +| C::C | false | 682 | 682 | C | +| C::operator= | false | 675 | 675 | operator= | +| C::~C | false | 614 | 614 | ~C | +| Error::Error | false | 259 | 259 | Error | +| Error::Error | false | 272 | 272 | Error | +| Error::Error | false | 277 | 277 | return ... | +| Error::Error | false | 279 | 279 | { ... } | +| Error::Error | true | 277 | 272 | | +| Error::Error | true | 279 | 277 | | +| Error::operator= | false | 253 | 253 | operator= | +| Error::~Error | false | 263 | 263 | ~Error | +| Error::~Error | false | 268 | 268 | return ... | +| Error::~Error | false | 270 | 270 | { ... } | +| Error::~Error | true | 268 | 263 | | +| Error::~Error | true | 270 | 268 | | +| __va_list_tag::operator= | false | 140 | 140 | operator= | +| __va_list_tag::operator= | false | 147 | 147 | operator= | +| f | false | 477 | 477 | f | +| f | false | 488 | 488 | declaration | +| f | false | 491 | 491 | call to C | +| f | false | 496 | 496 | 102 | +| f | false | 497 | 497 | initializer for c102 | +| f | false | 501 | 501 | call to C | +| f | false | 505 | 505 | 103 | +| f | false | 506 | 506 | initializer for c103 | +| f | false | 509 | 509 | declaration | +| f | false | 511 | 511 | b1 | +| f | false | 513 | 513 | (bool)... | +| f | false | 516 | 516 | 1 | +| f | false | 517 | 517 | throw ... | +| f | false | 519 | 519 | ExprStmt | +| f | false | 521 | 521 | { ... } | +| f | false | 523 | 523 | if (...) ... | +| f | false | 525 | 525 | declaration | +| f | false | 527 | 527 | { ... } | +| f | false | 534 | 534 | 1 | +| f | false | 536 | 536 | call to C | +| f | false | 540 | 540 | 104 | +| f | false | 541 | 541 | initializer for c104 | | f | false | 544 | 544 | declaration | | f | false | 546 | 546 | { ... } | -| f | false | 549 | 549 | call to C | -| f | false | 553 | 553 | 108 | -| f | false | 554 | 554 | initializer for c108 | -| f | false | 558 | 558 | declaration | -| f | false | 560 | 560 | { ... } | -| f | false | 562 | 562 | __try { ... } __finally { ... } | -| f | false | 565 | 565 | call to C | -| f | false | 569 | 569 | 109 | -| f | false | 570 | 570 | initializer for c109 | -| f | false | 574 | 574 | declaration | -| f | false | 576 | 576 | return ... | -| f | false | 578 | 578 | { ... } | -| f | false | 580 | 580 | c101 | -| f | false | 582 | 582 | call to c101.~C | -| f | false | 583 | 583 | c105 | -| f | false | 584 | 584 | call to c105.~C | -| f | false | 585 | 585 | c109 | -| f | false | 586 | 586 | call to c109.~C | -| f | false | 587 | 587 | c101 | -| f | false | 588 | 588 | call to c101.~C | -| f | false | 589 | 589 | c105 | -| f | false | 590 | 590 | call to c105.~C | -| f | false | 591 | 591 | c108 | -| f | false | 593 | 593 | call to c108.~C | -| f | false | 594 | 594 | c106 | -| f | false | 596 | 596 | call to c106.~C | -| f | false | 597 | 597 | c107 | -| f | false | 598 | 598 | call to c107.~C | -| f | false | 599 | 599 | c106 | -| f | false | 600 | 600 | call to c106.~C | -| f | false | 601 | 601 | c104 | -| f | false | 603 | 603 | call to c104.~C | -| f | false | 604 | 604 | c102 | -| f | false | 606 | 606 | call to c102.~C | -| f | false | 607 | 607 | c103 | -| f | false | 608 | 608 | call to c103.~C | -| f | false | 609 | 609 | c102 | -| f | false | 610 | 610 | call to c102.~C | -| f | true | 422 | 493 | | -| f | true | 426 | 422 | | -| f | true | 427 | 426 | | -| f | true | 431 | 427 | | -| f | true | 434 | 468 | | -| f | true | 438 | 434 | | -| f | true | 439 | 438 | | -| f | true | 444 | 607 | | -| f | true | 448 | 444 | | -| f | true | 449 | 448 | | -| f | true | 453 | 439 | | -| f | true | 455 | 466 | T | -| f | true | 455 | 470 | F | -| f | true | 461 | 462 | | -| f | true | 462 | 609 | | -| f | true | 464 | 461 | | -| f | true | 466 | 464 | | -| f | true | 468 | 455 | | -| f | true | 470 | 449 | | -| f | true | 472 | 453 | | -| f | true | 478 | 491 | T | -| f | true | 480 | 601 | | -| f | true | 484 | 480 | | -| f | true | 485 | 484 | | -| f | true | 489 | 485 | | -| f | true | 491 | 489 | | -| f | true | 493 | 472 | | -| f | true | 496 | 562 | | -| f | true | 500 | 496 | | -| f | true | 501 | 500 | | +| f | false | 548 | 548 | __try { ... } __except( ... ) { ... } | +| f | false | 550 | 550 | declaration | +| f | false | 553 | 553 | call to C | +| f | false | 557 | 557 | 106 | +| f | false | 558 | 558 | initializer for c106 | +| f | false | 562 | 562 | call to C | +| f | false | 566 | 566 | 107 | +| f | false | 567 | 567 | initializer for c107 | +| f | false | 570 | 570 | declaration | +| f | false | 572 | 572 | b2 | +| f | false | 574 | 574 | (bool)... | +| f | false | 577 | 577 | 2 | +| f | false | 578 | 578 | throw ... | +| f | false | 580 | 580 | ExprStmt | +| f | false | 582 | 582 | { ... } | +| f | false | 584 | 584 | if (...) ... | +| f | false | 586 | 586 | declaration | +| f | false | 588 | 588 | { ... } | +| f | false | 591 | 591 | call to C | +| f | false | 595 | 595 | 108 | +| f | false | 596 | 596 | initializer for c108 | +| f | false | 599 | 599 | declaration | +| f | false | 601 | 601 | { ... } | +| f | false | 603 | 603 | __try { ... } __finally { ... } | +| f | false | 605 | 605 | declaration | +| f | false | 607 | 607 | return ... | +| f | false | 609 | 609 | { ... } | +| f | false | 611 | 611 | c101 | +| f | false | 613 | 613 | call to c101.~C | +| f | false | 615 | 615 | c105 | +| f | false | 616 | 616 | call to c105.~C | +| f | false | 617 | 617 | c109 | +| f | false | 618 | 618 | call to c109.~C | +| f | false | 619 | 619 | c101 | +| f | false | 620 | 620 | call to c101.~C | +| f | false | 621 | 621 | c105 | +| f | false | 622 | 622 | call to c105.~C | +| f | false | 623 | 623 | c108 | +| f | false | 625 | 625 | call to c108.~C | +| f | false | 626 | 626 | c106 | +| f | false | 628 | 628 | call to c106.~C | +| f | false | 629 | 629 | c107 | +| f | false | 630 | 630 | call to c107.~C | +| f | false | 631 | 631 | c106 | +| f | false | 632 | 632 | call to c106.~C | +| f | false | 633 | 633 | c104 | +| f | false | 635 | 635 | call to c104.~C | +| f | false | 636 | 636 | c102 | +| f | false | 638 | 638 | call to c102.~C | +| f | false | 639 | 639 | c103 | +| f | false | 640 | 640 | call to c103.~C | +| f | false | 641 | 641 | c102 | +| f | false | 642 | 642 | call to c102.~C | +| f | false | 644 | 644 | call to C | +| f | false | 648 | 648 | 101 | +| f | false | 649 | 649 | initializer for c101 | +| f | false | 653 | 653 | call to C | +| f | false | 657 | 657 | 105 | +| f | false | 658 | 658 | initializer for c105 | +| f | false | 662 | 662 | call to C | +| f | false | 666 | 666 | 109 | +| f | false | 667 | 667 | initializer for c109 | +| f | true | 488 | 649 | | +| f | true | 491 | 523 | | +| f | true | 496 | 491 | | +| f | true | 497 | 496 | | +| f | true | 501 | 639 | | | f | true | 505 | 501 | | -| f | true | 508 | 542 | | -| f | true | 512 | 508 | | -| f | true | 513 | 512 | | -| f | true | 518 | 597 | | -| f | true | 522 | 518 | | -| f | true | 523 | 522 | | -| f | true | 527 | 513 | | -| f | true | 529 | 540 | T | -| f | true | 529 | 544 | F | -| f | true | 535 | 536 | | -| f | true | 536 | 599 | | -| f | true | 538 | 535 | | -| f | true | 540 | 538 | | -| f | true | 542 | 529 | | -| f | true | 544 | 523 | | -| f | true | 546 | 527 | | -| f | true | 549 | 591 | | -| f | true | 553 | 549 | | -| f | true | 554 | 553 | | -| f | true | 558 | 554 | | -| f | true | 560 | 558 | | -| f | true | 562 | 546 | | -| f | true | 565 | 576 | | -| f | true | 569 | 565 | | -| f | true | 570 | 569 | | -| f | true | 574 | 570 | | -| f | true | 576 | 585 | | -| f | true | 578 | 431 | | -| f | true | 580 | 582 | | -| f | true | 582 | 416 | | -| f | true | 583 | 584 | | -| f | true | 584 | 580 | | -| f | true | 585 | 586 | | -| f | true | 586 | 583 | | -| f | true | 587 | 588 | | -| f | true | 588 | 416 | | -| f | true | 589 | 590 | | -| f | true | 590 | 587 | | -| f | true | 591 | 593 | | -| f | true | 593 | 574 | | -| f | true | 593 | 589 | | -| f | true | 594 | 596 | | -| f | true | 596 | 560 | | -| f | true | 597 | 598 | | -| f | true | 598 | 594 | | -| f | true | 599 | 600 | | -| f | true | 600 | 560 | | -| f | true | 601 | 603 | | -| f | true | 603 | 505 | | -| f | true | 604 | 606 | | -| f | true | 606 | 505 | | -| f | true | 607 | 608 | | -| f | true | 608 | 604 | | -| f | true | 609 | 610 | | -| f | true | 610 | 478 | | -| f1 | false | 213 | 213 | f1 | -| f2 | false | 216 | 216 | f2 | -| f3 | false | 219 | 219 | f3 | -| f4 | false | 222 | 222 | f4 | -| f4 | false | 225 | 225 | return ... | -| f4 | false | 227 | 227 | { ... } | -| f4 | true | 225 | 222 | | -| f4 | true | 227 | 225 | | -| f5 | false | 230 | 230 | f5 | -| f5 | false | 235 | 235 | 3 | -| f5 | false | 236 | 236 | throw ... | -| f5 | false | 238 | 238 | ExprStmt | -| f5 | false | 240 | 240 | { ... } | -| f5 | true | 235 | 236 | | -| f5 | true | 236 | 230 | | -| f5 | true | 238 | 235 | | -| f5 | true | 240 | 238 | | -| fun | false | 267 | 267 | fun | -| fun | false | 272 | 272 | call to f1 | -| fun | false | 274 | 274 | ExprStmt | -| fun | false | 276 | 276 | call to f2 | -| fun | false | 278 | 278 | ExprStmt | -| fun | false | 280 | 280 | call to f3 | -| fun | false | 282 | 282 | ExprStmt | -| fun | false | 284 | 284 | call to f4 | -| fun | false | 286 | 286 | ExprStmt | -| fun | false | 288 | 288 | call to f5 | -| fun | false | 290 | 290 | ExprStmt | -| fun | false | 294 | 294 | 5 | -| fun | false | 295 | 295 | throw ... | +| f | true | 506 | 505 | | +| f | true | 509 | 497 | | +| f | true | 511 | 521 | T | +| f | true | 511 | 525 | F | +| f | true | 516 | 517 | | +| f | true | 517 | 641 | | +| f | true | 519 | 516 | | +| f | true | 521 | 519 | | +| f | true | 523 | 511 | | +| f | true | 525 | 506 | | +| f | true | 527 | 509 | | +| f | true | 534 | 546 | T | +| f | true | 536 | 633 | | +| f | true | 540 | 536 | | +| f | true | 541 | 540 | | +| f | true | 544 | 541 | | +| f | true | 546 | 544 | | +| f | true | 548 | 527 | | +| f | true | 550 | 658 | | +| f | true | 553 | 584 | | +| f | true | 557 | 553 | | +| f | true | 558 | 557 | | +| f | true | 562 | 629 | | +| f | true | 566 | 562 | | +| f | true | 567 | 566 | | +| f | true | 570 | 558 | | +| f | true | 572 | 582 | T | +| f | true | 572 | 586 | F | +| f | true | 577 | 578 | | +| f | true | 578 | 631 | | +| f | true | 580 | 577 | | +| f | true | 582 | 580 | | +| f | true | 584 | 572 | | +| f | true | 586 | 567 | | +| f | true | 588 | 570 | | +| f | true | 591 | 623 | | +| f | true | 595 | 591 | | +| f | true | 596 | 595 | | +| f | true | 599 | 596 | | +| f | true | 601 | 599 | | +| f | true | 603 | 588 | | +| f | true | 605 | 667 | | +| f | true | 607 | 617 | | +| f | true | 609 | 488 | | +| f | true | 611 | 613 | | +| f | true | 613 | 477 | | +| f | true | 615 | 616 | | +| f | true | 616 | 611 | | +| f | true | 617 | 618 | | +| f | true | 618 | 615 | | +| f | true | 619 | 620 | | +| f | true | 620 | 477 | | +| f | true | 621 | 622 | | +| f | true | 622 | 619 | | +| f | true | 623 | 625 | | +| f | true | 625 | 605 | | +| f | true | 625 | 621 | | +| f | true | 626 | 628 | | +| f | true | 628 | 601 | | +| f | true | 629 | 630 | | +| f | true | 630 | 626 | | +| f | true | 631 | 632 | | +| f | true | 632 | 601 | | +| f | true | 633 | 635 | | +| f | true | 635 | 550 | | +| f | true | 636 | 638 | | +| f | true | 638 | 550 | | +| f | true | 639 | 640 | | +| f | true | 640 | 636 | | +| f | true | 641 | 642 | | +| f | true | 642 | 534 | | +| f | true | 644 | 548 | | +| f | true | 648 | 644 | | +| f | true | 649 | 648 | | +| f | true | 653 | 603 | | +| f | true | 657 | 653 | | +| f | true | 658 | 657 | | +| f | true | 662 | 607 | | +| f | true | 666 | 662 | | +| f | true | 667 | 666 | | +| f1 | false | 292 | 292 | f1 | +| f2 | false | 299 | 299 | f2 | +| f3 | false | 304 | 304 | f3 | +| f4 | false | 309 | 309 | f4 | +| f4 | false | 433 | 433 | return ... | +| f4 | false | 435 | 435 | { ... } | +| f4 | true | 433 | 309 | | +| f4 | true | 435 | 433 | | +| f5 | false | 314 | 314 | f5 | +| f5 | false | 422 | 422 | 3 | +| f5 | false | 423 | 423 | throw ... | +| f5 | false | 425 | 425 | ExprStmt | +| f5 | false | 427 | 427 | { ... } | +| f5 | true | 422 | 423 | | +| f5 | true | 423 | 314 | | +| f5 | true | 425 | 422 | | +| f5 | true | 427 | 425 | | +| fun | false | 287 | 287 | fun | +| fun | false | 295 | 295 | call to f1 | | fun | false | 297 | 297 | ExprStmt | -| fun | false | 299 | 299 | call to g | -| fun | false | 301 | 301 | ExprStmt | -| fun | false | 303 | 303 | { ... } | -| fun | false | 308 | 308 | call to h | -| fun | false | 310 | 310 | ExprStmt | -| fun | false | 312 | 312 | { ... } | -| fun | false | 314 | 314 | | -| fun | false | 315 | 315 | try { ... } | -| fun | false | 317 | 317 | { ... } | -| fun | false | 322 | 322 | call to i | +| fun | false | 300 | 300 | call to f2 | +| fun | false | 302 | 302 | ExprStmt | +| fun | false | 305 | 305 | call to f3 | +| fun | false | 307 | 307 | ExprStmt | +| fun | false | 310 | 310 | call to f4 | +| fun | false | 312 | 312 | ExprStmt | +| fun | false | 315 | 315 | call to f5 | +| fun | false | 317 | 317 | ExprStmt | +| fun | false | 321 | 321 | 5 | +| fun | false | 322 | 322 | throw ... | | fun | false | 324 | 324 | ExprStmt | -| fun | false | 326 | 326 | { ... } | -| fun | false | 331 | 331 | call to j | -| fun | false | 333 | 333 | ExprStmt | -| fun | false | 335 | 335 | { ... } | -| fun | false | 337 | 337 | | -| fun | false | 338 | 338 | | -| fun | false | 339 | 339 | try { ... } | -| fun | false | 341 | 341 | call to k | -| fun | false | 343 | 343 | ExprStmt | -| fun | false | 347 | 347 | 7 | -| fun | false | 348 | 348 | throw ... | -| fun | false | 350 | 350 | ExprStmt | -| fun | false | 352 | 352 | { ... } | -| fun | false | 357 | 357 | call to l | -| fun | false | 359 | 359 | ExprStmt | -| fun | false | 361 | 361 | { ... } | -| fun | false | 363 | 363 | call to m | -| fun | false | 365 | 365 | ExprStmt | -| fun | false | 367 | 367 | { ... } | +| fun | false | 327 | 327 | call to g | +| fun | false | 329 | 329 | ExprStmt | +| fun | false | 331 | 331 | { ... } | +| fun | false | 337 | 337 | call to h | +| fun | false | 339 | 339 | ExprStmt | +| fun | false | 341 | 341 | { ... } | +| fun | false | 343 | 343 | | +| fun | false | 344 | 344 | try { ... } | +| fun | false | 346 | 346 | { ... } | +| fun | false | 352 | 352 | call to i | +| fun | false | 354 | 354 | ExprStmt | +| fun | false | 356 | 356 | { ... } | +| fun | false | 362 | 362 | call to j | +| fun | false | 364 | 364 | ExprStmt | +| fun | false | 366 | 366 | { ... } | +| fun | false | 368 | 368 | | | fun | false | 369 | 369 | | -| fun | false | 370 | 370 | | -| fun | false | 371 | 371 | try { ... } | -| fun | false | 373 | 373 | call to n | +| fun | false | 370 | 370 | try { ... } | +| fun | false | 373 | 373 | call to k | | fun | false | 375 | 375 | ExprStmt | -| fun | false | 377 | 377 | return ... | -| fun | false | 379 | 379 | { ... } | -| fun | true | 272 | 278 | | -| fun | true | 274 | 272 | | -| fun | true | 276 | 282 | | -| fun | true | 278 | 276 | | -| fun | true | 280 | 286 | | -| fun | true | 282 | 280 | | -| fun | true | 284 | 290 | | -| fun | true | 286 | 284 | | -| fun | true | 290 | 288 | | -| fun | true | 294 | 295 | | -| fun | true | 295 | 314 | | -| fun | true | 297 | 294 | | -| fun | true | 299 | 343 | | -| fun | true | 301 | 299 | | -| fun | true | 303 | 274 | | -| fun | true | 308 | 343 | | -| fun | true | 310 | 308 | | +| fun | false | 379 | 379 | 7 | +| fun | false | 380 | 380 | throw ... | +| fun | false | 382 | 382 | ExprStmt | +| fun | false | 384 | 384 | { ... } | +| fun | false | 390 | 390 | call to l | +| fun | false | 392 | 392 | ExprStmt | +| fun | false | 394 | 394 | { ... } | +| fun | false | 397 | 397 | call to m | +| fun | false | 399 | 399 | ExprStmt | +| fun | false | 401 | 401 | { ... } | +| fun | false | 403 | 403 | | +| fun | false | 404 | 404 | | +| fun | false | 405 | 405 | try { ... } | +| fun | false | 408 | 408 | call to n | +| fun | false | 410 | 410 | ExprStmt | +| fun | false | 412 | 412 | return ... | +| fun | false | 414 | 414 | { ... } | +| fun | true | 295 | 302 | | +| fun | true | 297 | 295 | | +| fun | true | 300 | 307 | | +| fun | true | 302 | 300 | | +| fun | true | 305 | 312 | | +| fun | true | 307 | 305 | | +| fun | true | 310 | 317 | | | fun | true | 312 | 310 | | -| fun | true | 314 | 312 | | -| fun | true | 314 | 337 | | -| fun | true | 315 | 303 | | | fun | true | 317 | 315 | | +| fun | true | 321 | 322 | | | fun | true | 322 | 343 | | -| fun | true | 324 | 322 | | -| fun | true | 326 | 324 | | -| fun | true | 331 | 343 | | -| fun | true | 333 | 331 | | -| fun | true | 335 | 333 | | -| fun | true | 337 | 326 | | -| fun | true | 337 | 338 | | -| fun | true | 338 | 267 | | -| fun | true | 338 | 335 | | -| fun | true | 339 | 317 | | -| fun | true | 341 | 371 | | +| fun | true | 324 | 321 | | +| fun | true | 327 | 375 | | +| fun | true | 329 | 327 | | +| fun | true | 331 | 297 | | +| fun | true | 337 | 375 | | +| fun | true | 339 | 337 | | +| fun | true | 341 | 339 | | | fun | true | 343 | 341 | | -| fun | true | 347 | 348 | | -| fun | true | 348 | 369 | | -| fun | true | 350 | 347 | | -| fun | true | 352 | 350 | | -| fun | true | 357 | 375 | | -| fun | true | 359 | 357 | | -| fun | true | 361 | 359 | | -| fun | true | 363 | 375 | | -| fun | true | 365 | 363 | | -| fun | true | 367 | 365 | | -| fun | true | 369 | 361 | | -| fun | true | 369 | 370 | | -| fun | true | 370 | 367 | | -| fun | true | 371 | 352 | | -| fun | true | 373 | 377 | | +| fun | true | 343 | 368 | | +| fun | true | 344 | 331 | | +| fun | true | 346 | 344 | | +| fun | true | 352 | 375 | | +| fun | true | 354 | 352 | | +| fun | true | 356 | 354 | | +| fun | true | 362 | 375 | | +| fun | true | 364 | 362 | | +| fun | true | 366 | 364 | | +| fun | true | 368 | 356 | | +| fun | true | 368 | 369 | | +| fun | true | 369 | 287 | | +| fun | true | 369 | 366 | | +| fun | true | 370 | 346 | | +| fun | true | 373 | 405 | | | fun | true | 375 | 373 | | -| fun | true | 377 | 267 | | -| fun | true | 379 | 339 | | -| fun2 | false | 101 | 101 | fun2 | -| fun2 | false | 105 | 105 | { ... } | -| fun2 | false | 108 | 108 | re-throw exception | -| fun2 | false | 110 | 110 | ExprStmt | -| fun2 | false | 112 | 112 | { ... } | -| fun2 | false | 116 | 116 | 1 | -| fun2 | false | 117 | 117 | return ... | -| fun2 | false | 119 | 119 | { ... } | -| fun2 | false | 121 | 121 | | -| fun2 | false | 122 | 122 | | -| fun2 | false | 123 | 123 | try { ... } | -| fun2 | false | 127 | 127 | 0 | -| fun2 | false | 128 | 128 | return ... | -| fun2 | false | 130 | 130 | { ... } | -| fun2 | false | 141 | 141 | fun2 | -| fun2 | false | 144 | 144 | { ... } | -| fun2 | false | 150 | 150 | re-throw exception | -| fun2 | false | 152 | 152 | ExprStmt | -| fun2 | false | 154 | 154 | { ... } | -| fun2 | false | 157 | 157 | 1 | -| fun2 | false | 158 | 158 | return ... | -| fun2 | false | 160 | 160 | { ... } | -| fun2 | false | 162 | 162 | | -| fun2 | false | 163 | 163 | | -| fun2 | false | 164 | 164 | try { ... } | -| fun2 | false | 167 | 167 | 0 | -| fun2 | false | 168 | 168 | return ... | -| fun2 | false | 170 | 170 | { ... } | -| fun2 | true | 105 | 128 | | -| fun2 | true | 108 | 101 | | -| fun2 | true | 110 | 108 | | -| fun2 | true | 112 | 110 | | -| fun2 | true | 116 | 101 | | -| fun2 | true | 117 | 116 | | -| fun2 | true | 119 | 117 | | -| fun2 | true | 121 | 112 | | -| fun2 | true | 121 | 122 | | -| fun2 | true | 122 | 119 | | -| fun2 | true | 123 | 105 | | -| fun2 | true | 127 | 101 | | -| fun2 | true | 128 | 127 | | -| fun2 | true | 130 | 123 | | -| fun2 | true | 144 | 168 | | -| fun2 | true | 150 | 141 | | -| fun2 | true | 152 | 150 | | -| fun2 | true | 154 | 152 | | -| fun2 | true | 157 | 141 | | -| fun2 | true | 158 | 157 | | -| fun2 | true | 160 | 158 | | -| fun2 | true | 162 | 154 | | -| fun2 | true | 162 | 163 | | -| fun2 | true | 163 | 160 | | -| fun2 | true | 164 | 144 | | -| fun2 | true | 167 | 141 | | -| fun2 | true | 168 | 167 | | -| fun2 | true | 170 | 164 | | -| g | false | 243 | 243 | g | -| h | false | 246 | 246 | h | -| i | false | 249 | 249 | i | -| j | false | 252 | 252 | j | -| k | false | 255 | 255 | k | -| l | false | 258 | 258 | l | -| m | false | 261 | 261 | m | -| n | false | 264 | 264 | n | -| run_fun2 | false | 136 | 136 | run_fun2 | -| run_fun2 | false | 172 | 172 | call to fun2 | -| run_fun2 | false | 174 | 174 | ExprStmt | -| run_fun2 | false | 176 | 176 | return ... | -| run_fun2 | false | 178 | 178 | { ... } | -| run_fun2 | true | 172 | 176 | | -| run_fun2 | true | 174 | 172 | | -| run_fun2 | true | 176 | 136 | | -| run_fun2 | true | 178 | 174 | | +| fun | true | 379 | 380 | | +| fun | true | 380 | 403 | | +| fun | true | 382 | 379 | | +| fun | true | 384 | 382 | | +| fun | true | 390 | 410 | | +| fun | true | 392 | 390 | | +| fun | true | 394 | 392 | | +| fun | true | 397 | 410 | | +| fun | true | 399 | 397 | | +| fun | true | 401 | 399 | | +| fun | true | 403 | 394 | | +| fun | true | 403 | 404 | | +| fun | true | 404 | 401 | | +| fun | true | 405 | 384 | | +| fun | true | 408 | 412 | | +| fun | true | 410 | 408 | | +| fun | true | 412 | 287 | | +| fun | true | 414 | 370 | | +| fun2 | false | 204 | 204 | fun2 | +| fun2 | false | 215 | 215 | fun2 | +| fun2 | false | 218 | 218 | { ... } | +| fun2 | false | 223 | 223 | re-throw exception | +| fun2 | false | 225 | 225 | ExprStmt | +| fun2 | false | 227 | 227 | { ... } | +| fun2 | false | 231 | 231 | 1 | +| fun2 | false | 232 | 232 | return ... | +| fun2 | false | 234 | 234 | { ... } | +| fun2 | false | 236 | 236 | | +| fun2 | false | 237 | 237 | | +| fun2 | false | 238 | 238 | try { ... } | +| fun2 | false | 242 | 242 | 0 | +| fun2 | false | 243 | 243 | return ... | +| fun2 | false | 245 | 245 | { ... } | +| fun2 | false | 702 | 702 | { ... } | +| fun2 | false | 707 | 707 | re-throw exception | +| fun2 | false | 708 | 708 | ExprStmt | +| fun2 | false | 709 | 709 | { ... } | +| fun2 | false | 711 | 711 | 1 | +| fun2 | false | 712 | 712 | return ... | +| fun2 | false | 713 | 713 | { ... } | +| fun2 | false | 714 | 714 | | +| fun2 | false | 715 | 715 | | +| fun2 | false | 716 | 716 | try { ... } | +| fun2 | false | 718 | 718 | 0 | +| fun2 | false | 719 | 719 | return ... | +| fun2 | false | 720 | 720 | { ... } | +| fun2 | true | 218 | 243 | | +| fun2 | true | 223 | 215 | | +| fun2 | true | 225 | 223 | | +| fun2 | true | 227 | 225 | | +| fun2 | true | 231 | 215 | | +| fun2 | true | 232 | 231 | | +| fun2 | true | 234 | 232 | | +| fun2 | true | 236 | 227 | | +| fun2 | true | 236 | 237 | | +| fun2 | true | 237 | 234 | | +| fun2 | true | 238 | 218 | | +| fun2 | true | 242 | 215 | | +| fun2 | true | 243 | 242 | | +| fun2 | true | 245 | 238 | | +| fun2 | true | 702 | 719 | | +| fun2 | true | 707 | 204 | | +| fun2 | true | 708 | 707 | | +| fun2 | true | 709 | 708 | | +| fun2 | true | 711 | 204 | | +| fun2 | true | 712 | 711 | | +| fun2 | true | 713 | 712 | | +| fun2 | true | 714 | 709 | | +| fun2 | true | 714 | 715 | | +| fun2 | true | 715 | 713 | | +| fun2 | true | 716 | 702 | | +| fun2 | true | 718 | 204 | | +| fun2 | true | 719 | 718 | | +| fun2 | true | 720 | 716 | | +| g | false | 326 | 326 | g | +| h | false | 336 | 336 | h | +| i | false | 351 | 351 | i | +| j | false | 361 | 361 | j | +| k | false | 372 | 372 | k | +| l | false | 389 | 389 | l | +| m | false | 396 | 396 | m | +| n | false | 407 | 407 | n | +| run_fun2 | false | 199 | 199 | run_fun2 | +| run_fun2 | false | 207 | 207 | call to fun2 | +| run_fun2 | false | 209 | 209 | ExprStmt | +| run_fun2 | false | 211 | 211 | return ... | +| run_fun2 | false | 213 | 213 | { ... } | +| run_fun2 | true | 207 | 211 | | +| run_fun2 | true | 209 | 207 | | +| run_fun2 | true | 211 | 199 | | +| run_fun2 | true | 213 | 209 | | diff --git a/cpp/ql/test/library-tests/clang_builtin_macros/addressof.c b/cpp/ql/test/library-tests/clang_builtin_macros/addressof.c new file mode 100644 index 000000000000..becaef159cc2 --- /dev/null +++ b/cpp/ql/test/library-tests/clang_builtin_macros/addressof.c @@ -0,0 +1,8 @@ +// semmle-extractor-options: --edg --clang + +int x = 0; + +int* f(void) { + int* x_addr = __builtin_addressof(x); + return x_addr; +} diff --git a/cpp/ql/test/library-tests/clang_builtin_macros/addressof.expected b/cpp/ql/test/library-tests/clang_builtin_macros/addressof.expected new file mode 100644 index 000000000000..62c43a08f689 --- /dev/null +++ b/cpp/ql/test/library-tests/clang_builtin_macros/addressof.expected @@ -0,0 +1 @@ +| addressof.c:6:17:6:38 | __builtin_addressof ... | addressof.c:6:37:6:37 | x | diff --git a/cpp/ql/test/library-tests/clang_builtin_macros/addressof.ql b/cpp/ql/test/library-tests/clang_builtin_macros/addressof.ql new file mode 100644 index 000000000000..42a1f1e563a6 --- /dev/null +++ b/cpp/ql/test/library-tests/clang_builtin_macros/addressof.ql @@ -0,0 +1,4 @@ +import cpp + +from BuiltInOperationBuiltInAddressOf op +select op, op.getOperand() diff --git a/cpp/ql/test/library-tests/clang_builtin_macros/values.expected b/cpp/ql/test/library-tests/clang_builtin_macros/values.expected index 5e33ee8c946a..e8f119eace50 100644 --- a/cpp/ql/test/library-tests/clang_builtin_macros/values.expected +++ b/cpp/ql/test/library-tests/clang_builtin_macros/values.expected @@ -1,7 +1,9 @@ -| | fp_offset | | -| | gp_offset | | -| | overflow_arg_area | | -| | reg_save_area | | +| | fp_offset | | +| | gp_offset | | +| | overflow_arg_area | | +| | reg_save_area | | +| addressof.c | x | 0 | +| addressof.c | x_addr | __builtin_addressof ... | | extended.cpp | has_nullptr_e | 1 | | extended.cpp | has_nullptr_f | 1 | | gcc492.c | has_include | 1 | diff --git a/cpp/ql/test/library-tests/clang_builtin_macros/values.ql b/cpp/ql/test/library-tests/clang_builtin_macros/values.ql index a5ac1514a9bd..70fba76eb391 100644 --- a/cpp/ql/test/library-tests/clang_builtin_macros/values.ql +++ b/cpp/ql/test/library-tests/clang_builtin_macros/values.ql @@ -1,9 +1,9 @@ import cpp string varInit(Variable v) { - if exists(v.getInitializer().getExpr().getValue()) - then result = v.getInitializer().getExpr().getValue().toString() - else result = "" + if exists(v.getInitializer().getExpr()) + then result = v.getInitializer().getExpr().toString() + else result = "" } from Variable v diff --git a/cpp/ql/test/library-tests/clang_ms/element.expected b/cpp/ql/test/library-tests/clang_ms/element.expected index 763649f774f6..a44924bcbc7b 100644 --- a/cpp/ql/test/library-tests/clang_ms/element.expected +++ b/cpp/ql/test/library-tests/clang_ms/element.expected @@ -51,6 +51,7 @@ | file://:0:0:0:0 | __uptr | | file://:0:0:0:0 | __va_list_tag | | file://:0:0:0:0 | __va_list_tag & | +| file://:0:0:0:0 | __va_list_tag && | | file://:0:0:0:0 | abstract | | file://:0:0:0:0 | atomic | | file://:0:0:0:0 | auto | @@ -60,12 +61,13 @@ | file://:0:0:0:0 | char16_t | | file://:0:0:0:0 | char32_t | | file://:0:0:0:0 | const | +| file://:0:0:0:0 | const __va_list_tag | +| file://:0:0:0:0 | const __va_list_tag & | | file://:0:0:0:0 | const mystruct | | file://:0:0:0:0 | const mystruct & | | file://:0:0:0:0 | declaration of 1st parameter | | file://:0:0:0:0 | declaration of 1st parameter | | file://:0:0:0:0 | decltype(nullptr) | -| file://:0:0:0:0 | definition of __va_list_tag | | file://:0:0:0:0 | definition of fp_offset | | file://:0:0:0:0 | definition of gp_offset | | file://:0:0:0:0 | definition of overflow_arg_area | @@ -104,6 +106,8 @@ | file://:0:0:0:0 | override | | file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | private | | file://:0:0:0:0 | protected | | file://:0:0:0:0 | public | diff --git a/cpp/ql/test/library-tests/classes/base_classes/base_classes.cpp b/cpp/ql/test/library-tests/classes/base_classes/base_classes.cpp index 48da325c7bd6..b4fdb7da1be5 100644 --- a/cpp/ql/test/library-tests/classes/base_classes/base_classes.cpp +++ b/cpp/ql/test/library-tests/classes/base_classes/base_classes.cpp @@ -9,8 +9,7 @@ template struct S : indirect::real { }; /* -Currently 'indirect' isn't in the database; the base class is -simply 'empty'. We might want to also include 'indirect', with a +Currently S's base class is simply 'empty'. We might want a way to reach the unevaluated 'indirect::real'. */ diff --git a/cpp/ql/test/library-tests/classes/base_classes/base_classes.expected b/cpp/ql/test/library-tests/classes/base_classes/base_classes.expected index eb6db93c3bfa..ed3c8ce78eb8 100644 --- a/cpp/ql/test/library-tests/classes/base_classes/base_classes.expected +++ b/cpp/ql/test/library-tests/classes/base_classes/base_classes.expected @@ -1,5 +1,6 @@ | base_classes.cpp:1:8:1:12 | empty | | | base_classes.cpp:4:8:4:15 | indirect | | +| base_classes.cpp:4:8:4:15 | indirect | | | base_classes.cpp:9:8:9:8 | S | empty | | base_classes.cpp:9:8:9:8 | S | empty | | file://:0:0:0:0 | __va_list_tag | | diff --git a/cpp/ql/test/library-tests/conditions/elements.expected b/cpp/ql/test/library-tests/conditions/elements.expected index 3a4d6ced0ed0..ec507ee9a924 100644 --- a/cpp/ql/test/library-tests/conditions/elements.expected +++ b/cpp/ql/test/library-tests/conditions/elements.expected @@ -30,6 +30,7 @@ | file://:0:0:0:0 | __uptr | | file://:0:0:0:0 | __va_list_tag | | file://:0:0:0:0 | __va_list_tag & | +| file://:0:0:0:0 | __va_list_tag && | | file://:0:0:0:0 | abstract | | file://:0:0:0:0 | atomic | | file://:0:0:0:0 | auto | @@ -39,9 +40,10 @@ | file://:0:0:0:0 | char16_t | | file://:0:0:0:0 | char32_t | | file://:0:0:0:0 | const | +| file://:0:0:0:0 | const __va_list_tag | +| file://:0:0:0:0 | const __va_list_tag & | | file://:0:0:0:0 | decltype(nullptr) | | file://:0:0:0:0 | definition of | -| file://:0:0:0:0 | definition of __va_list_tag | | file://:0:0:0:0 | definition of fp_offset | | file://:0:0:0:0 | definition of gp_offset | | file://:0:0:0:0 | definition of overflow_arg_area | @@ -77,6 +79,8 @@ | file://:0:0:0:0 | optional | | file://:0:0:0:0 | overflow_arg_area | | file://:0:0:0:0 | override | +| file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | private | | file://:0:0:0:0 | protected | | file://:0:0:0:0 | public | diff --git a/cpp/ql/test/library-tests/conditions/vars.expected b/cpp/ql/test/library-tests/conditions/vars.expected index f90baf9ebc17..e2395ad5141a 100644 --- a/cpp/ql/test/library-tests/conditions/vars.expected +++ b/cpp/ql/test/library-tests/conditions/vars.expected @@ -2,5 +2,7 @@ | file://:0:0:0:0 | fp_offset | | file://:0:0:0:0 | gp_offset | | file://:0:0:0:0 | overflow_arg_area | +| file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | reg_save_area | | test.cpp:3:12:3:12 | | diff --git a/cpp/ql/test/library-tests/cpp_builtin_types/bool/Variables.expected b/cpp/ql/test/library-tests/cpp_builtin_types/bool/Variables.expected index 130e5f396ac9..cee1ff31e8e4 100644 --- a/cpp/ql/test/library-tests/cpp_builtin_types/bool/Variables.expected +++ b/cpp/ql/test/library-tests/cpp_builtin_types/bool/Variables.expected @@ -3,4 +3,6 @@ | file://:0:0:0:0 | fp_offset | file://:0:0:0:0 | unsigned int | | file://:0:0:0:0 | gp_offset | file://:0:0:0:0 | unsigned int | | file://:0:0:0:0 | overflow_arg_area | file://:0:0:0:0 | void * | +| file://:0:0:0:0 | p#0 | file://:0:0:0:0 | __va_list_tag & | +| file://:0:0:0:0 | p#0 | file://:0:0:0:0 | __va_list_tag && | | file://:0:0:0:0 | reg_save_area | file://:0:0:0:0 | void * | diff --git a/cpp/ql/test/library-tests/cpp_builtin_types/wchar_t/Variables.expected b/cpp/ql/test/library-tests/cpp_builtin_types/wchar_t/Variables.expected index 8afb9b307a69..ae1905962b6b 100644 --- a/cpp/ql/test/library-tests/cpp_builtin_types/wchar_t/Variables.expected +++ b/cpp/ql/test/library-tests/cpp_builtin_types/wchar_t/Variables.expected @@ -2,4 +2,6 @@ | file://:0:0:0:0 | fp_offset | | file://:0:0:0:0 | gp_offset | | file://:0:0:0:0 | overflow_arg_area | +| file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | reg_save_area | diff --git a/cpp/ql/test/library-tests/declarationEntry/more/declarationEntry.expected b/cpp/ql/test/library-tests/declarationEntry/more/declarationEntry.expected index 4f89ef48c486..47c5f075a412 100644 --- a/cpp/ql/test/library-tests/declarationEntry/more/declarationEntry.expected +++ b/cpp/ql/test/library-tests/declarationEntry/more/declarationEntry.expected @@ -6,7 +6,6 @@ | file://:0:0:0:0 | declaration of 1st parameter | | file://:0:0:0:0 | declaration of 1st parameter | | file://:0:0:0:0 | declaration of 1st parameter | -| file://:0:0:0:0 | definition of __va_list_tag | | file://:0:0:0:0 | definition of fp_offset | | file://:0:0:0:0 | definition of gp_offset | | file://:0:0:0:0 | definition of overflow_arg_area | @@ -61,7 +60,11 @@ | test.cpp:57:7:57:26 | definition of tmplClassProtoAndDef | | test.cpp:59:19:59:19 | definition of T | | test.cpp:60:6:60:29 | declaration of tmplInstantiatedFunction | +| test.cpp:60:33:60:33 | definition of t | +| test.cpp:60:33:60:33 | definition of t | | test.cpp:61:19:61:19 | definition of T | +| test.cpp:62:6:62:6 | definition of tmplInstantiatedFunction | +| test.cpp:62:6:62:6 | definition of tmplInstantiatedFunction | | test.cpp:62:6:62:29 | definition of tmplInstantiatedFunction | | test.cpp:62:33:62:33 | declaration of t | | test.cpp:62:33:62:33 | definition of t | diff --git a/cpp/ql/test/library-tests/declarationEntry/template/declarationEntry.expected b/cpp/ql/test/library-tests/declarationEntry/template/declarationEntry.expected index 9273ca6a569f..84b1e1bf4194 100644 --- a/cpp/ql/test/library-tests/declarationEntry/template/declarationEntry.expected +++ b/cpp/ql/test/library-tests/declarationEntry/template/declarationEntry.expected @@ -11,7 +11,7 @@ | src4.cpp:6:20:6:21 | definition of zz | | src5.cpp:5:16:5:19 | definition of elem | | src5.cpp:6:8:6:17 | definition of my_istream | -| src5.cpp:10:8:10:17 | declaration of my_istream | +| src5.cpp:10:8:10:23 | declaration of my_istream | | src5.fwd.hpp:6:17:6:20 | definition of elem | | src5.fwd.hpp:6:29:6:38 | declaration of my_istream | | src6.cpp:5:19:5:23 | definition of mis_c | diff --git a/cpp/ql/test/library-tests/declarationEntry/template/usertype.expected b/cpp/ql/test/library-tests/declarationEntry/template/usertype.expected index 30490ce36575..76c7d0ea2d23 100644 --- a/cpp/ql/test/library-tests/declarationEntry/template/usertype.expected +++ b/cpp/ql/test/library-tests/declarationEntry/template/usertype.expected @@ -11,5 +11,5 @@ | src4.cpp:4:7:4:20 | template_class<> | 1 | | src5.cpp:5:16:5:19 | elem | 1 | | src5.cpp:6:8:6:17 | my_istream | 1 | -| src5.cpp:10:8:10:17 | my_istream | 1 | +| src5.cpp:10:8:10:23 | my_istream | 1 | | src5.fwd.hpp:6:17:6:20 | elem | 1 | diff --git a/cpp/ql/test/library-tests/default_parameters/variables.expected b/cpp/ql/test/library-tests/default_parameters/variables.expected index 3dcb5d017f99..9763fd11f9cf 100644 --- a/cpp/ql/test/library-tests/default_parameters/variables.expected +++ b/cpp/ql/test/library-tests/default_parameters/variables.expected @@ -1,6 +1,8 @@ | file://:0:0:0:0 | fp_offset | 1 | | 1 | | | file://:0:0:0:0 | gp_offset | 1 | | 1 | | | file://:0:0:0:0 | overflow_arg_area | 1 | | 1 | | +| file://:0:0:0:0 | p#0 | 1 | | 1 | | +| file://:0:0:0:0 | p#0 | 1 | | 1 | | | file://:0:0:0:0 | reg_save_area | 1 | | 1 | | | test.cpp:2:21:2:21 | x | 1 | file://:0:0:0:0 initializer for x | 2 | test.cpp:2:25:2:26 10 | | test.cpp:2:21:2:21 | x | 1 | file://:0:0:0:0 initializer for x | 2 | test.cpp:3:9:3:9 3 | diff --git a/cpp/ql/test/library-tests/depends_initializers/VariableInitializers.expected b/cpp/ql/test/library-tests/depends_initializers/VariableInitializers.expected index a7698cee885b..f126ef4c9eb0 100644 --- a/cpp/ql/test/library-tests/depends_initializers/VariableInitializers.expected +++ b/cpp/ql/test/library-tests/depends_initializers/VariableInitializers.expected @@ -9,6 +9,8 @@ | file://:0:0:0:0 | p#0 | 0 | 0 | | file://:0:0:0:0 | p#0 | 0 | 0 | | file://:0:0:0:0 | p#0 | 0 | 0 | +| file://:0:0:0:0 | p#0 | 0 | 0 | +| file://:0:0:0:0 | p#0 | 0 | 0 | | file://:0:0:0:0 | reg_save_area | 0 | 0 | | initializers.cpp:8:8:8:12 | a_ptr | 1 | 1 | | initializers.cpp:9:4:9:4 | a | 1 | 1 | diff --git a/cpp/ql/test/library-tests/derived_types/DerivedTypesBaseType.expected b/cpp/ql/test/library-tests/derived_types/DerivedTypesBaseType.expected index 68e2d7e841c2..bacdb9e73b3a 100644 --- a/cpp/ql/test/library-tests/derived_types/DerivedTypesBaseType.expected +++ b/cpp/ql/test/library-tests/derived_types/DerivedTypesBaseType.expected @@ -5,6 +5,9 @@ | file://:0:0:0:0 | CC * | derivedtype.cpp:5:11:5:12 | CC | | file://:0:0:0:0 | CC ** | file://:0:0:0:0 | CC * | | file://:0:0:0:0 | __va_list_tag & | file://:0:0:0:0 | __va_list_tag | +| file://:0:0:0:0 | __va_list_tag && | file://:0:0:0:0 | __va_list_tag | | file://:0:0:0:0 | const C | derivedtype.cpp:1:7:1:7 | C | | file://:0:0:0:0 | const C & | file://:0:0:0:0 | const C | +| file://:0:0:0:0 | const __va_list_tag | file://:0:0:0:0 | __va_list_tag | +| file://:0:0:0:0 | const __va_list_tag & | file://:0:0:0:0 | const __va_list_tag | | file://:0:0:0:0 | void * | file://:0:0:0:0 | void | diff --git a/cpp/ql/test/library-tests/destructors/cfg.expected b/cpp/ql/test/library-tests/destructors/cfg.expected index cf730468263e..f037a8c19358 100644 --- a/cpp/ql/test/library-tests/destructors/cfg.expected +++ b/cpp/ql/test/library-tests/destructors/cfg.expected @@ -1,255 +1,255 @@ -| C::C | false | 135 | 135 | C | -| C::C | false | 138 | 138 | C | -| C::operator= | false | 133 | 133 | operator= | -| C::~C | false | 137 | 137 | ~C | -| Class2::Class2 | false | 382 | 382 | Class2 | -| Class2::Class2 | false | 388 | 388 | return ... | -| Class2::Class2 | false | 390 | 390 | { ... } | -| Class2::Class2 | false | 395 | 395 | Class2 | -| Class2::Class2 | true | 388 | 382 | | -| Class2::Class2 | true | 390 | 388 | | -| Class2::operator= | false | 392 | 392 | operator= | -| Class2::~Class2 | false | 394 | 394 | ~Class2 | -| Outer::Inner::Inner | false | 429 | 429 | Inner | -| Outer::Inner::Inner | false | 434 | 434 | return ... | -| Outer::Inner::Inner | false | 436 | 436 | { ... } | -| Outer::Inner::Inner | false | 498 | 498 | Inner | -| Outer::Inner::Inner | true | 434 | 429 | | -| Outer::Inner::Inner | true | 436 | 434 | | -| Outer::Inner::operator= | false | 496 | 496 | operator= | -| Outer::Inner::~Inner | false | 472 | 472 | ~Inner | -| Outer::Inner::~Inner | false | 476 | 476 | return ... | -| Outer::Inner::~Inner | false | 478 | 478 | { ... } | -| Outer::Inner::~Inner | true | 476 | 472 | | -| Outer::Inner::~Inner | true | 478 | 476 | | -| Outer::f2 | false | 411 | 411 | f2 | -| Outer::f2 | false | 417 | 417 | call to getClass2 | -| Outer::f2 | false | 419 | 419 | initializer for c | -| Outer::f2 | false | 424 | 424 | call to Inner | -| Outer::f2 | false | 438 | 438 | c | -| Outer::f2 | false | 440 | 440 | (const Class2)... | -| Outer::f2 | false | 442 | 442 | (reference to) | -| Outer::f2 | false | 444 | 444 | initializer for inner | -| Outer::f2 | false | 448 | 448 | declaration | -| Outer::f2 | false | 450 | 450 | i | -| Outer::f2 | false | 452 | 452 | (bool)... | -| Outer::f2 | false | 454 | 454 | return ... | -| Outer::f2 | false | 456 | 456 | { ... } | -| Outer::f2 | false | 458 | 458 | if (...) ... | -| Outer::f2 | false | 460 | 460 | declaration | -| Outer::f2 | false | 462 | 462 | return ... | -| Outer::f2 | false | 464 | 464 | { ... } | -| Outer::f2 | false | 466 | 466 | c | -| Outer::f2 | false | 468 | 468 | call to c.~Class2 | -| Outer::f2 | false | 469 | 469 | inner | -| Outer::f2 | false | 470 | 470 | call to inner.~Inner | -| Outer::f2 | true | 417 | 458 | | -| Outer::f2 | true | 419 | 417 | | -| Outer::f2 | true | 424 | 462 | | -| Outer::f2 | true | 438 | 424 | | -| Outer::f2 | true | 444 | 438 | | -| Outer::f2 | true | 448 | 419 | | -| Outer::f2 | true | 450 | 456 | T | -| Outer::f2 | true | 450 | 460 | F | -| Outer::f2 | true | 454 | 466 | | -| Outer::f2 | true | 456 | 454 | | -| Outer::f2 | true | 458 | 450 | | -| Outer::f2 | true | 460 | 444 | | -| Outer::f2 | true | 462 | 469 | | -| Outer::f2 | true | 464 | 448 | | -| Outer::f2 | true | 466 | 468 | | -| Outer::f2 | true | 468 | 411 | | -| Outer::f2 | true | 469 | 470 | | -| Outer::f2 | true | 470 | 466 | | -| Outer::operator= | false | 481 | 481 | operator= | -| Outer::operator= | false | 485 | 485 | operator= | -| __va_list_tag::operator= | false | 64 | 64 | operator= | -| __va_list_tag::operator= | false | 68 | 68 | operator= | -| f | false | 152 | 152 | f | -| f | false | 158 | 158 | call to C | -| f | false | 162 | 162 | 110 | -| f | false | 163 | 163 | initializer for c10 | -| f | false | 168 | 168 | call to C | -| f | false | 172 | 172 | 111 | -| f | false | 173 | 173 | initializer for c11 | -| f | false | 178 | 178 | call to C | -| f | false | 182 | 182 | 120 | -| f | false | 183 | 183 | initializer for c20 | -| f | false | 188 | 188 | call to C | -| f | false | 192 | 192 | 121 | -| f | false | 193 | 193 | initializer for c21 | -| f | false | 198 | 198 | call to C | -| f | false | 202 | 202 | 130 | -| f | false | 203 | 203 | initializer for c30 | -| f | false | 208 | 208 | call to C | -| f | false | 212 | 212 | 131 | -| f | false | 213 | 213 | initializer for c31 | -| f | false | 218 | 218 | call to C | -| f | false | 222 | 222 | 132 | -| f | false | 223 | 223 | initializer for c32 | -| f | false | 228 | 228 | call to C | -| f | false | 232 | 232 | 133 | -| f | false | 233 | 233 | initializer for c33 | +| C::C | false | 197 | 197 | C | +| C::C | false | 398 | 398 | C | +| C::operator= | false | 391 | 391 | operator= | +| C::~C | false | 331 | 331 | ~C | +| Class2::Class2 | false | 538 | 538 | Class2 | +| Class2::Class2 | false | 544 | 544 | return ... | +| Class2::Class2 | false | 546 | 546 | { ... } | +| Class2::Class2 | false | 547 | 547 | Class2 | +| Class2::Class2 | true | 544 | 538 | | +| Class2::Class2 | true | 546 | 544 | | +| Class2::operator= | false | 532 | 532 | operator= | +| Class2::~Class2 | false | 467 | 467 | ~Class2 | +| Outer::Inner::Inner | false | 488 | 488 | Inner | +| Outer::Inner::Inner | false | 509 | 509 | Inner | +| Outer::Inner::Inner | false | 528 | 528 | return ... | +| Outer::Inner::Inner | false | 530 | 530 | { ... } | +| Outer::Inner::Inner | true | 528 | 488 | | +| Outer::Inner::Inner | true | 530 | 528 | | +| Outer::Inner::operator= | false | 502 | 502 | operator= | +| Outer::Inner::~Inner | false | 470 | 470 | ~Inner | +| Outer::Inner::~Inner | false | 517 | 517 | return ... | +| Outer::Inner::~Inner | false | 519 | 519 | { ... } | +| Outer::Inner::~Inner | true | 517 | 470 | | +| Outer::Inner::~Inner | true | 519 | 517 | | +| Outer::f2 | false | 439 | 439 | f2 | +| Outer::f2 | false | 447 | 447 | declaration | +| Outer::f2 | false | 449 | 449 | i | +| Outer::f2 | false | 451 | 451 | (bool)... | +| Outer::f2 | false | 452 | 452 | return ... | +| Outer::f2 | false | 454 | 454 | { ... } | +| Outer::f2 | false | 456 | 456 | if (...) ... | +| Outer::f2 | false | 458 | 458 | declaration | +| Outer::f2 | false | 460 | 460 | return ... | +| Outer::f2 | false | 462 | 462 | { ... } | +| Outer::f2 | false | 464 | 464 | c | +| Outer::f2 | false | 466 | 466 | call to c.~Class2 | +| Outer::f2 | false | 468 | 468 | inner | +| Outer::f2 | false | 469 | 469 | call to inner.~Inner | +| Outer::f2 | false | 474 | 474 | call to getClass2 | +| Outer::f2 | false | 476 | 476 | initializer for c | +| Outer::f2 | false | 481 | 481 | call to Inner | +| Outer::f2 | false | 490 | 490 | c | +| Outer::f2 | false | 492 | 492 | (const Class2)... | +| Outer::f2 | false | 493 | 493 | (reference to) | +| Outer::f2 | false | 494 | 494 | initializer for inner | +| Outer::f2 | true | 447 | 476 | | +| Outer::f2 | true | 449 | 454 | T | +| Outer::f2 | true | 449 | 458 | F | +| Outer::f2 | true | 452 | 464 | | +| Outer::f2 | true | 454 | 452 | | +| Outer::f2 | true | 456 | 449 | | +| Outer::f2 | true | 458 | 494 | | +| Outer::f2 | true | 460 | 468 | | +| Outer::f2 | true | 462 | 447 | | +| Outer::f2 | true | 464 | 466 | | +| Outer::f2 | true | 466 | 439 | | +| Outer::f2 | true | 468 | 469 | | +| Outer::f2 | true | 469 | 464 | | +| Outer::f2 | true | 474 | 456 | | +| Outer::f2 | true | 476 | 474 | | +| Outer::f2 | true | 481 | 460 | | +| Outer::f2 | true | 490 | 481 | | +| Outer::f2 | true | 494 | 490 | | +| Outer::operator= | false | 424 | 424 | operator= | +| Outer::operator= | false | 435 | 435 | operator= | +| __va_list_tag::operator= | false | 93 | 93 | operator= | +| __va_list_tag::operator= | false | 100 | 100 | operator= | +| f | false | 181 | 181 | f | +| f | false | 192 | 192 | declaration | +| f | false | 195 | 195 | call to C | +| f | false | 200 | 200 | 120 | +| f | false | 201 | 201 | initializer for c20 | +| f | false | 205 | 205 | call to C | +| f | false | 209 | 209 | 121 | +| f | false | 210 | 210 | initializer for c21 | +| f | false | 213 | 213 | declaration | +| f | false | 216 | 216 | call to C | +| f | false | 220 | 220 | 130 | +| f | false | 221 | 221 | initializer for c30 | +| f | false | 224 | 224 | declaration | +| f | false | 226 | 226 | { ... } | +| f | false | 229 | 229 | call to C | +| f | false | 233 | 233 | 131 | +| f | false | 234 | 234 | initializer for c31 | | f | false | 238 | 238 | call to C | -| f | false | 242 | 242 | 134 | -| f | false | 243 | 243 | initializer for c34 | -| f | false | 248 | 248 | call to C | -| f | false | 252 | 252 | 122 | -| f | false | 253 | 253 | initializer for c22 | -| f | false | 258 | 258 | call to C | -| f | false | 262 | 262 | 123 | -| f | false | 263 | 263 | initializer for c23 | -| f | false | 267 | 267 | declaration | -| f | false | 269 | 269 | declaration | -| f | false | 271 | 271 | declaration | -| f | false | 273 | 273 | { ... } | -| f | false | 275 | 275 | declaration | -| f | false | 277 | 277 | b1 | -| f | false | 279 | 279 | (bool)... | -| f | false | 281 | 281 | goto ... | -| f | false | 283 | 283 | if (...) ... | -| f | false | 285 | 285 | declaration | -| f | false | 287 | 287 | b2 | -| f | false | 289 | 289 | (bool)... | -| f | false | 291 | 291 | return ... | -| f | false | 293 | 293 | if (...) ... | -| f | false | 295 | 295 | declaration | -| f | false | 297 | 297 | { ... } | -| f | false | 299 | 299 | declaration | -| f | false | 301 | 301 | { ... } | +| f | false | 242 | 242 | 132 | +| f | false | 243 | 243 | initializer for c32 | +| f | false | 247 | 247 | call to C | +| f | false | 251 | 251 | 133 | +| f | false | 252 | 252 | initializer for c33 | +| f | false | 255 | 255 | declaration | +| f | false | 257 | 257 | b1 | +| f | false | 259 | 259 | (bool)... | +| f | false | 260 | 260 | goto ... | +| f | false | 262 | 262 | if (...) ... | +| f | false | 264 | 264 | declaration | +| f | false | 266 | 266 | b2 | +| f | false | 268 | 268 | (bool)... | +| f | false | 269 | 269 | return ... | +| f | false | 271 | 271 | if (...) ... | +| f | false | 273 | 273 | declaration | +| f | false | 275 | 275 | { ... } | +| f | false | 278 | 278 | call to C | +| f | false | 282 | 282 | 134 | +| f | false | 283 | 283 | initializer for c34 | +| f | false | 286 | 286 | declaration | +| f | false | 288 | 288 | { ... } | +| f | false | 290 | 290 | declaration | +| f | false | 292 | 292 | { ... } | +| f | false | 295 | 295 | call to C | +| f | false | 299 | 299 | 122 | +| f | false | 300 | 300 | initializer for c22 | | f | false | 303 | 303 | declaration | | f | false | 305 | 305 | { ... } | -| f | false | 307 | 307 | declaration | -| f | false | 309 | 309 | { ... } | -| f | false | 311 | 311 | label ...: | -| f | false | 313 | 313 | declaration | -| f | false | 315 | 315 | { ... } | -| f | false | 317 | 317 | declaration | -| f | false | 319 | 319 | return ... | -| f | false | 321 | 321 | { ... } | -| f | false | 323 | 323 | c10 | -| f | false | 325 | 325 | call to c10.~C | -| f | false | 326 | 326 | c11 | -| f | false | 327 | 327 | call to c11.~C | -| f | false | 328 | 328 | c23 | -| f | false | 330 | 330 | call to c23.~C | -| f | false | 331 | 331 | c22 | -| f | false | 333 | 333 | call to c22.~C | -| f | false | 334 | 334 | c20 | -| f | false | 336 | 336 | call to c20.~C | -| f | false | 337 | 337 | c21 | -| f | false | 338 | 338 | call to c21.~C | -| f | false | 339 | 339 | c34 | -| f | false | 341 | 341 | call to c34.~C | -| f | false | 342 | 342 | c31 | -| f | false | 344 | 344 | call to c31.~C | -| f | false | 345 | 345 | c32 | -| f | false | 346 | 346 | call to c32.~C | -| f | false | 347 | 347 | c33 | -| f | false | 348 | 348 | call to c33.~C | -| f | false | 349 | 349 | c20 | -| f | false | 350 | 350 | call to c20.~C | -| f | false | 351 | 351 | c31 | -| f | false | 352 | 352 | call to c31.~C | -| f | false | 353 | 353 | c32 | -| f | false | 354 | 354 | call to c32.~C | +| f | false | 308 | 308 | call to C | +| f | false | 312 | 312 | 123 | +| f | false | 313 | 313 | initializer for c23 | +| f | false | 316 | 316 | label ...: | +| f | false | 318 | 318 | declaration | +| f | false | 320 | 320 | { ... } | +| f | false | 322 | 322 | declaration | +| f | false | 324 | 324 | return ... | +| f | false | 326 | 326 | { ... } | +| f | false | 328 | 328 | c10 | +| f | false | 330 | 330 | call to c10.~C | +| f | false | 332 | 332 | c11 | +| f | false | 333 | 333 | call to c11.~C | +| f | false | 334 | 334 | c23 | +| f | false | 336 | 336 | call to c23.~C | +| f | false | 337 | 337 | c22 | +| f | false | 339 | 339 | call to c22.~C | +| f | false | 340 | 340 | c20 | +| f | false | 342 | 342 | call to c20.~C | +| f | false | 343 | 343 | c21 | +| f | false | 344 | 344 | call to c21.~C | +| f | false | 345 | 345 | c34 | +| f | false | 347 | 347 | call to c34.~C | +| f | false | 348 | 348 | c31 | +| f | false | 350 | 350 | call to c31.~C | +| f | false | 351 | 351 | c32 | +| f | false | 352 | 352 | call to c32.~C | +| f | false | 353 | 353 | c33 | +| f | false | 354 | 354 | call to c33.~C | | f | false | 355 | 355 | c20 | | f | false | 356 | 356 | call to c20.~C | | f | false | 357 | 357 | c31 | | f | false | 358 | 358 | call to c31.~C | -| f | false | 359 | 359 | c30 | -| f | false | 361 | 361 | call to c30.~C | -| f | true | 158 | 305 | | -| f | true | 162 | 158 | | -| f | true | 163 | 162 | | -| f | true | 168 | 319 | | -| f | true | 172 | 168 | | -| f | true | 173 | 172 | | -| f | true | 178 | 273 | | -| f | true | 182 | 178 | | -| f | true | 183 | 182 | | -| f | true | 188 | 337 | | -| f | true | 192 | 188 | | -| f | true | 193 | 192 | | -| f | true | 198 | 359 | | -| f | true | 202 | 198 | | -| f | true | 203 | 202 | | -| f | true | 208 | 283 | | -| f | true | 212 | 208 | | -| f | true | 213 | 212 | | -| f | true | 218 | 293 | | -| f | true | 222 | 218 | | -| f | true | 223 | 222 | | -| f | true | 228 | 347 | | -| f | true | 232 | 228 | | -| f | true | 233 | 232 | | -| f | true | 238 | 339 | | +| f | false | 359 | 359 | c32 | +| f | false | 360 | 360 | call to c32.~C | +| f | false | 361 | 361 | c20 | +| f | false | 362 | 362 | call to c20.~C | +| f | false | 363 | 363 | c31 | +| f | false | 364 | 364 | call to c31.~C | +| f | false | 365 | 365 | c30 | +| f | false | 367 | 367 | call to c30.~C | +| f | false | 369 | 369 | call to C | +| f | false | 373 | 373 | 110 | +| f | false | 374 | 374 | initializer for c10 | +| f | false | 378 | 378 | call to C | +| f | false | 382 | 382 | 111 | +| f | false | 383 | 383 | initializer for c11 | +| f | true | 192 | 374 | | +| f | true | 195 | 226 | | +| f | true | 200 | 195 | | +| f | true | 201 | 200 | | +| f | true | 205 | 343 | | +| f | true | 209 | 205 | | +| f | true | 210 | 209 | | +| f | true | 213 | 201 | | +| f | true | 216 | 365 | | +| f | true | 220 | 216 | | +| f | true | 221 | 220 | | +| f | true | 224 | 221 | | +| f | true | 226 | 224 | | +| f | true | 229 | 262 | | +| f | true | 233 | 229 | | +| f | true | 234 | 233 | | +| f | true | 238 | 271 | | | f | true | 242 | 238 | | | f | true | 243 | 242 | | -| f | true | 248 | 331 | | -| f | true | 252 | 248 | | -| f | true | 253 | 252 | | -| f | true | 258 | 328 | | -| f | true | 262 | 258 | | -| f | true | 263 | 262 | | -| f | true | 267 | 163 | | -| f | true | 269 | 183 | | -| f | true | 271 | 203 | | -| f | true | 273 | 271 | | -| f | true | 275 | 213 | | -| f | true | 277 | 281 | T | -| f | true | 277 | 285 | F | -| f | true | 281 | 357 | | -| f | true | 283 | 277 | | -| f | true | 285 | 223 | | -| f | true | 287 | 291 | T | -| f | true | 287 | 295 | F | -| f | true | 291 | 353 | | -| f | true | 293 | 287 | | -| f | true | 295 | 233 | | -| f | true | 297 | 275 | | -| f | true | 299 | 243 | | -| f | true | 301 | 299 | | -| f | true | 303 | 193 | | -| f | true | 305 | 269 | | -| f | true | 307 | 253 | | -| f | true | 309 | 307 | | -| f | true | 311 | 313 | | -| f | true | 313 | 263 | | -| f | true | 315 | 311 | | -| f | true | 317 | 173 | | -| f | true | 319 | 326 | | -| f | true | 321 | 267 | | -| f | true | 323 | 325 | | -| f | true | 325 | 152 | | -| f | true | 326 | 327 | | -| f | true | 327 | 323 | | +| f | true | 247 | 353 | | +| f | true | 251 | 247 | | +| f | true | 252 | 251 | | +| f | true | 255 | 234 | | +| f | true | 257 | 260 | T | +| f | true | 257 | 264 | F | +| f | true | 260 | 363 | | +| f | true | 262 | 257 | | +| f | true | 264 | 243 | | +| f | true | 266 | 269 | T | +| f | true | 266 | 273 | F | +| f | true | 269 | 359 | | +| f | true | 271 | 266 | | +| f | true | 273 | 252 | | +| f | true | 275 | 255 | | +| f | true | 278 | 345 | | +| f | true | 282 | 278 | | +| f | true | 283 | 282 | | +| f | true | 286 | 283 | | +| f | true | 288 | 286 | | +| f | true | 290 | 210 | | +| f | true | 292 | 213 | | +| f | true | 295 | 337 | | +| f | true | 299 | 295 | | +| f | true | 300 | 299 | | +| f | true | 303 | 300 | | +| f | true | 305 | 303 | | +| f | true | 308 | 334 | | +| f | true | 312 | 308 | | +| f | true | 313 | 312 | | +| f | true | 316 | 318 | | +| f | true | 318 | 313 | | +| f | true | 320 | 316 | | +| f | true | 322 | 383 | | +| f | true | 324 | 332 | | +| f | true | 326 | 192 | | | f | true | 328 | 330 | | -| f | true | 330 | 317 | | -| f | true | 331 | 333 | | -| f | true | 333 | 315 | | +| f | true | 330 | 181 | | +| f | true | 332 | 333 | | +| f | true | 333 | 328 | | | f | true | 334 | 336 | | -| f | true | 336 | 309 | | -| f | true | 337 | 338 | | -| f | true | 338 | 334 | | -| f | true | 339 | 341 | | -| f | true | 341 | 303 | | -| f | true | 342 | 344 | | -| f | true | 344 | 301 | | -| f | true | 345 | 346 | | -| f | true | 346 | 342 | | -| f | true | 347 | 348 | | -| f | true | 348 | 345 | | -| f | true | 349 | 350 | | -| f | true | 350 | 323 | | +| f | true | 336 | 322 | | +| f | true | 337 | 339 | | +| f | true | 339 | 320 | | +| f | true | 340 | 342 | | +| f | true | 342 | 305 | | +| f | true | 343 | 344 | | +| f | true | 344 | 340 | | +| f | true | 345 | 347 | | +| f | true | 347 | 290 | | +| f | true | 348 | 350 | | +| f | true | 350 | 288 | | | f | true | 351 | 352 | | -| f | true | 352 | 349 | | +| f | true | 352 | 348 | | | f | true | 353 | 354 | | | f | true | 354 | 351 | | | f | true | 355 | 356 | | -| f | true | 356 | 311 | | +| f | true | 356 | 328 | | | f | true | 357 | 358 | | | f | true | 358 | 355 | | -| f | true | 359 | 361 | | -| f | true | 361 | 297 | | -| getClass2 | false | 405 | 405 | getClass2 | +| f | true | 359 | 360 | | +| f | true | 360 | 357 | | +| f | true | 361 | 362 | | +| f | true | 362 | 316 | | +| f | true | 363 | 364 | | +| f | true | 364 | 361 | | +| f | true | 365 | 367 | | +| f | true | 367 | 275 | | +| f | true | 369 | 292 | | +| f | true | 373 | 369 | | +| f | true | 374 | 373 | | +| f | true | 378 | 324 | | +| f | true | 382 | 378 | | +| f | true | 383 | 382 | | +| getClass2 | false | 420 | 420 | getClass2 | diff --git a/cpp/ql/test/library-tests/floats/float128/functions.expected b/cpp/ql/test/library-tests/floats/float128/functions.expected index 75ed9f32d96f..d1412363d8f2 100644 --- a/cpp/ql/test/library-tests/floats/float128/functions.expected +++ b/cpp/ql/test/library-tests/floats/float128/functions.expected @@ -8,6 +8,8 @@ | operator= | file://:0:0:0:0 | __is_floating_point_helper & | file://:0:0:0:0 | const __is_floating_point_helper & | | operator= | file://:0:0:0:0 | __is_floating_point_helper & | file://:0:0:0:0 | __is_floating_point_helper && | | operator= | file://:0:0:0:0 | __is_floating_point_helper & | file://:0:0:0:0 | const __is_floating_point_helper & | +| operator= | file://:0:0:0:0 | __va_list_tag & | file://:0:0:0:0 | __va_list_tag && | +| operator= | file://:0:0:0:0 | __va_list_tag & | file://:0:0:0:0 | const __va_list_tag & | | operator= | file://:0:0:0:0 | false_type & | file://:0:0:0:0 | const false_type & | | operator= | file://:0:0:0:0 | false_type & | file://:0:0:0:0 | false_type && | | operator= | file://:0:0:0:0 | true_type & | file://:0:0:0:0 | const true_type & | diff --git a/cpp/ql/test/library-tests/functions/functions/FunctionCalls.expected b/cpp/ql/test/library-tests/functions/functions/FunctionCalls.expected index f06cec1daddd..6d6c1cb9ff12 100644 --- a/cpp/ql/test/library-tests/functions/functions/FunctionCalls.expected +++ b/cpp/ql/test/library-tests/functions/functions/FunctionCalls.expected @@ -1,3 +1,3 @@ -| ODASA-5186.cpp:17:13:17:13 | call to operator!= | file://:0:0:0:0 | operator!= | -| ODASA-5186.hpp:4:83:4:83 | call to operator== | ODASA-5186.cpp:5:8:5:17 | operator== | +| ODASA-5186.cpp:17:13:17:13 | call to operator!= | ODASA-5186.hpp:4:18:4:27 | operator!= | +| ODASA-5186.hpp:4:83:4:83 | call to operator== | ODASA-5186.cpp:5:8:5:8 | operator== | | functions.cpp:16:4:16:5 | call to ag | functions.cpp:11:7:11:8 | ag | diff --git a/cpp/ql/test/library-tests/functions/functions/Functions1.expected b/cpp/ql/test/library-tests/functions/functions/Functions1.expected index d812efa97390..20b892107223 100644 --- a/cpp/ql/test/library-tests/functions/functions/Functions1.expected +++ b/cpp/ql/test/library-tests/functions/functions/Functions1.expected @@ -1,14 +1,16 @@ | ODASA-5186.cpp:4:8:4:8 | operator= | operator= | | | ODASA-5186.cpp:4:8:4:8 | declaration | | ODASA-5186.cpp:4:8:4:8 | operator= | operator= | | | ODASA-5186.cpp:4:8:4:8 | declaration | +| ODASA-5186.cpp:5:8:5:8 | operator== | operator== | | | ODASA-5186.cpp:5:8:5:8 | declaration | +| ODASA-5186.cpp:5:8:5:8 | operator== | operator== | | | ODASA-5186.cpp:5:8:5:8 | definition | | ODASA-5186.cpp:5:8:5:17 | operator== | operator== | | | ODASA-5186.cpp:5:8:5:17 | declaration | -| ODASA-5186.cpp:5:8:5:17 | operator== | operator== | | | ODASA-5186.cpp:5:8:5:17 | declaration | -| ODASA-5186.cpp:5:8:5:17 | operator== | operator== | | | ODASA-5186.cpp:5:8:5:17 | definition | | ODASA-5186.cpp:5:8:5:17 | operator== | operator== | | | ODASA-5186.cpp:5:8:5:17 | definition | | ODASA-5186.cpp:8:6:8:9 | test | test | isTopLevel | TopLevelFunction | ODASA-5186.cpp:8:6:8:9 | declaration | | ODASA-5186.cpp:8:6:8:9 | test | test | isTopLevel | TopLevelFunction | ODASA-5186.cpp:8:6:8:9 | definition | | ODASA-5186.hpp:2:8:2:8 | operator= | operator= | | | ODASA-5186.hpp:2:8:2:8 | declaration | | ODASA-5186.hpp:2:8:2:8 | operator= | operator= | | | ODASA-5186.hpp:2:8:2:8 | declaration | | ODASA-5186.hpp:4:18:4:27 | operator!= | operator!= | isTopLevel | TopLevelFunction | ODASA-5186.hpp:4:18:4:27 | declaration | +| ODASA-5186.hpp:4:18:4:27 | operator!= | operator!= | isTopLevel | TopLevelFunction | ODASA-5186.hpp:4:18:4:27 | declaration | +| ODASA-5186.hpp:4:18:4:27 | operator!= | operator!= | isTopLevel | TopLevelFunction | ODASA-5186.hpp:4:18:4:27 | definition | | ODASA-5186.hpp:4:18:4:27 | operator!= | operator!= | isTopLevel | TopLevelFunction | ODASA-5186.hpp:4:18:4:27 | definition | | functions.cpp:1:6:1:6 | f | f | isTopLevel | TopLevelFunction | functions.cpp:1:6:1:6 | declaration | | functions.cpp:1:6:1:6 | f | f | isTopLevel | TopLevelFunction | functions.cpp:1:6:1:6 | definition | diff --git a/cpp/ql/test/library-tests/functions/functions/Functions2.expected b/cpp/ql/test/library-tests/functions/functions/Functions2.expected index 91de943e049c..7dbd0c5de8bf 100644 --- a/cpp/ql/test/library-tests/functions/functions/Functions2.expected +++ b/cpp/ql/test/library-tests/functions/functions/Functions2.expected @@ -1,7 +1,7 @@ | ODASA-5186.cpp:4:8:4:14 | MyClass | Class | ODASA-5186.cpp:5:8:5:17 | operator== | member function | | ODASA-5186.cpp:4:8:4:14 | MyClass | Struct | ODASA-5186.cpp:4:8:4:8 | operator= | member function | | ODASA-5186.cpp:4:8:4:14 | MyClass | Struct | ODASA-5186.cpp:4:8:4:8 | operator= | member function | -| ODASA-5186.cpp:4:8:4:14 | MyClass | Struct | ODASA-5186.cpp:5:8:5:17 | operator== | member function | +| ODASA-5186.cpp:4:8:4:14 | MyClass | Struct | ODASA-5186.cpp:5:8:5:8 | operator== | member function | | ODASA-5186.hpp:2:8:2:17 | NEQ_helper> | Struct | ODASA-5186.hpp:2:8:2:8 | operator= | member function | | ODASA-5186.hpp:2:8:2:17 | NEQ_helper> | Struct | ODASA-5186.hpp:2:8:2:8 | operator= | member function | | file://:0:0:0:0 | __va_list_tag | Struct | file://:0:0:0:0 | operator= | member function | diff --git a/cpp/ql/test/library-tests/functions/qualifiers/test.expected b/cpp/ql/test/library-tests/functions/qualifiers/test.expected index a7f8ef34c91d..0e60569320eb 100644 --- a/cpp/ql/test/library-tests/functions/qualifiers/test.expected +++ b/cpp/ql/test/library-tests/functions/qualifiers/test.expected @@ -1 +1,2 @@ | file://:0:0:0:0 | ..(..) | true | +| file://:0:0:0:0 | const __va_list_tag | true | diff --git a/cpp/ql/test/library-tests/instantiations/test.expected b/cpp/ql/test/library-tests/instantiations/test.expected index 44db55b6b37d..3e23680b5fbb 100644 --- a/cpp/ql/test/library-tests/instantiations/test.expected +++ b/cpp/ql/test/library-tests/instantiations/test.expected @@ -28,6 +28,6 @@ | test.cpp:19:7:19:7 | C | | test.cpp:19:7:19:7 | operator= | | test.cpp:19:7:19:7 | operator= | -| test.cpp:21:18:21:21 | vfun | +| test.cpp:21:18:21:18 | vfun | | test.cpp:21:18:21:21 | vfun | | test.cpp:27:6:27:6 | f | diff --git a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected index 19784c3c0c15..b01c82d135df 100644 --- a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected +++ b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected @@ -1,7 +1,11 @@ -#-----| __va_list_tag::operator=() -> __va_list_tag & +#-----| __va_list_tag::operator=(__va_list_tag &&) -> __va_list_tag & #-----| params: -#-----| __va_list_tag::operator=() -> __va_list_tag & +#-----| 0: p#0 +#-----| Type = __va_list_tag && +#-----| __va_list_tag::operator=(const __va_list_tag &) -> __va_list_tag & #-----| params: +#-----| 0: p#0 +#-----| Type = const __va_list_tag & #-----| operator delete(void *, unsigned long) -> void #-----| params: #-----| 0: p#0 diff --git a/cpp/ql/test/library-tests/lambdas/captures/elements.expected b/cpp/ql/test/library-tests/lambdas/captures/elements.expected index 52f85b0392df..384744c10800 100644 --- a/cpp/ql/test/library-tests/lambdas/captures/elements.expected +++ b/cpp/ql/test/library-tests/lambdas/captures/elements.expected @@ -115,7 +115,10 @@ | file://:0:0:0:0 | ..(*)(..) | | file://:0:0:0:0 | __va_list_tag | | file://:0:0:0:0 | __va_list_tag & | +| file://:0:0:0:0 | __va_list_tag && | | file://:0:0:0:0 | auto | +| file://:0:0:0:0 | const __va_list_tag | +| file://:0:0:0:0 | const __va_list_tag & | | file://:0:0:0:0 | const foo | | file://:0:0:0:0 | const foo & | | file://:0:0:0:0 | const lambda [] type at line 3, col. 5 | @@ -142,7 +145,6 @@ | file://:0:0:0:0 | decltype([...](...){...}) | | file://:0:0:0:0 | decltype([...](...){...}) | | file://:0:0:0:0 | decltype([...](...){...}) | -| file://:0:0:0:0 | definition of __va_list_tag | | file://:0:0:0:0 | definition of fp_offset | | file://:0:0:0:0 | definition of gp_offset | | file://:0:0:0:0 | definition of overflow_arg_area | @@ -176,6 +178,8 @@ | file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | reg_save_area | | file://:0:0:0:0 | this | | file://:0:0:0:0 | this | diff --git a/cpp/ql/test/library-tests/lambdas/cfg/cfg.expected b/cpp/ql/test/library-tests/lambdas/cfg/cfg.expected index cd73b85be0cb..18a4ec40e799 100644 --- a/cpp/ql/test/library-tests/lambdas/cfg/cfg.expected +++ b/cpp/ql/test/library-tests/lambdas/cfg/cfg.expected @@ -1,103 +1,103 @@ -| | false | 102 | 102 | operator() | -| | false | 108 | 108 | declaration | -| | false | 110 | 110 | call to printf | -| | false | 114 | 114 | Running with %d and %d\n | -| | false | 115 | 115 | array to pointer conversion | -| | false | 117 | 117 | (char *)... | -| | false | 119 | 119 | x | -| | false | 121 | 121 | y | -| | false | 123 | 123 | ExprStmt | -| | false | 125 | 125 | z | -| | false | 127 | 127 | x | -| | false | 129 | 129 | y | -| | false | 131 | 131 | ... + ... | -| | false | 133 | 133 | ... = ... | -| | false | 135 | 135 | ExprStmt | -| | false | 137 | 137 | call to printf | -| | false | 141 | 141 | Returning %d %d\n | -| | false | 142 | 142 | array to pointer conversion | -| | false | 144 | 144 | (char *)... | -| | false | 146 | 146 | z | -| | false | 148 | 148 | z | -| | false | 150 | 150 | ExprStmt | -| | false | 152 | 152 | z | -| | false | 154 | 154 | return ... | -| | false | 156 | 156 | { ... } | -| | false | 160 | 160 | operator auto (*)(int, int)->int | -| | false | 161 | 161 | _FUN | -| | false | 164 | 164 | _FUN | -| | false | 166 | 166 | return ... | -| | false | 168 | 168 | { ... } | +| | false | 154 | 154 | (constructor) | +| | false | 157 | 157 | (constructor) | +| | false | 164 | 164 | return ... | +| | false | 166 | 166 | { ... } | +| | false | 167 | 167 | operator= | | | false | 173 | 173 | (constructor) | -| | false | 175 | 175 | return ... | -| | false | 177 | 177 | { ... } | -| | false | 183 | 183 | (constructor) | -| | false | 186 | 186 | operator= | -| | false | 188 | 188 | (constructor) | -| | true | 108 | 123 | | -| | true | 110 | 135 | | -| | true | 114 | 119 | | -| | true | 119 | 121 | | -| | true | 121 | 110 | | -| | true | 123 | 114 | | -| | true | 125 | 133 | | -| | true | 127 | 129 | | -| | true | 129 | 131 | | -| | true | 131 | 125 | | -| | true | 133 | 150 | | -| | true | 135 | 127 | | -| | true | 137 | 154 | | -| | true | 141 | 146 | | -| | true | 146 | 148 | | -| | true | 148 | 137 | | -| | true | 150 | 141 | | -| | true | 152 | 102 | | -| | true | 154 | 152 | | -| | true | 156 | 108 | | -| | true | 164 | 160 | | +| | false | 175 | 175 | _FUN | +| | false | 184 | 184 | operator auto (*)(int, int)->int | +| | false | 190 | 190 | _FUN | +| | false | 192 | 192 | return ... | +| | false | 194 | 194 | { ... } | +| | false | 195 | 195 | operator() | +| | false | 206 | 206 | declaration | +| | false | 212 | 212 | call to printf | +| | false | 220 | 220 | Running with %d and %d\n | +| | false | 221 | 221 | array to pointer conversion | +| | false | 222 | 222 | (char *)... | +| | false | 223 | 223 | x | +| | false | 225 | 225 | y | +| | false | 227 | 227 | ExprStmt | +| | false | 232 | 232 | z | +| | false | 234 | 234 | x | +| | false | 236 | 236 | y | +| | false | 238 | 238 | ... + ... | +| | false | 240 | 240 | ... = ... | +| | false | 242 | 242 | ExprStmt | +| | false | 244 | 244 | call to printf | +| | false | 250 | 250 | Returning %d %d\n | +| | false | 251 | 251 | array to pointer conversion | +| | false | 252 | 252 | (char *)... | +| | false | 253 | 253 | z | +| | false | 255 | 255 | z | +| | false | 257 | 257 | ExprStmt | +| | false | 259 | 259 | z | +| | false | 261 | 261 | return ... | +| | false | 263 | 263 | { ... } | +| | true | 164 | 157 | | | | true | 166 | 164 | | -| | true | 168 | 166 | | -| | true | 175 | 173 | | -| | true | 177 | 175 | | -| __va_list_tag::operator= | false | 60 | 60 | operator= | -| __va_list_tag::operator= | false | 66 | 66 | operator= | -| main | false | 95 | 95 | main | -| main | false | 199 | 199 | [...](...){...} | -| main | false | 201 | 201 | initializer for myLambda | -| main | false | 205 | 205 | declaration | -| main | false | 207 | 207 | call to printf | -| main | false | 213 | 213 | Some results: %d %d\n | -| main | false | 214 | 214 | array to pointer conversion | -| main | false | 216 | 216 | (char *)... | -| main | false | 220 | 220 | call to operator() | -| main | false | 222 | 222 | myLambda | -| main | false | 224 | 224 | (const lambda [] type at line 12, col. 21)... | -| main | false | 228 | 228 | 5 | -| main | false | 231 | 231 | 6 | -| main | false | 232 | 232 | call to operator() | -| main | false | 234 | 234 | myLambda | -| main | false | 236 | 236 | (const lambda [] type at line 12, col. 21)... | -| main | false | 240 | 240 | 7 | -| main | false | 243 | 243 | 8 | -| main | false | 244 | 244 | ExprStmt | -| main | false | 248 | 248 | 0 | -| main | false | 249 | 249 | return ... | -| main | false | 251 | 251 | { ... } | -| main | true | 199 | 244 | | -| main | true | 201 | 199 | | -| main | true | 205 | 201 | | -| main | true | 207 | 249 | | -| main | true | 213 | 228 | | -| main | true | 220 | 240 | | -| main | true | 222 | 220 | | -| main | true | 228 | 231 | | -| main | true | 231 | 222 | | -| main | true | 232 | 207 | | -| main | true | 234 | 232 | | -| main | true | 240 | 243 | | -| main | true | 243 | 234 | | -| main | true | 244 | 213 | | -| main | true | 248 | 95 | | -| main | true | 249 | 248 | | -| main | true | 251 | 205 | | -| printf | false | 84 | 84 | printf | +| | true | 190 | 184 | | +| | true | 192 | 190 | | +| | true | 194 | 192 | | +| | true | 206 | 227 | | +| | true | 212 | 242 | | +| | true | 220 | 223 | | +| | true | 223 | 225 | | +| | true | 225 | 212 | | +| | true | 227 | 220 | | +| | true | 232 | 240 | | +| | true | 234 | 236 | | +| | true | 236 | 238 | | +| | true | 238 | 232 | | +| | true | 240 | 257 | | +| | true | 242 | 234 | | +| | true | 244 | 261 | | +| | true | 250 | 253 | | +| | true | 253 | 255 | | +| | true | 255 | 244 | | +| | true | 257 | 250 | | +| | true | 259 | 195 | | +| | true | 261 | 259 | | +| | true | 263 | 206 | | +| __va_list_tag::operator= | false | 92 | 92 | operator= | +| __va_list_tag::operator= | false | 99 | 99 | operator= | +| main | false | 147 | 147 | main | +| main | false | 269 | 269 | declaration | +| main | false | 271 | 271 | call to printf | +| main | false | 277 | 277 | Some results: %d %d\n | +| main | false | 278 | 278 | array to pointer conversion | +| main | false | 279 | 279 | (char *)... | +| main | false | 282 | 282 | call to operator() | +| main | false | 285 | 285 | [...](...){...} | +| main | false | 287 | 287 | initializer for myLambda | +| main | false | 291 | 291 | myLambda | +| main | false | 293 | 293 | (const lambda [] type at line 12, col. 21)... | +| main | false | 296 | 296 | 5 | +| main | false | 299 | 299 | 6 | +| main | false | 300 | 300 | call to operator() | +| main | false | 302 | 302 | myLambda | +| main | false | 304 | 304 | (const lambda [] type at line 12, col. 21)... | +| main | false | 307 | 307 | 7 | +| main | false | 310 | 310 | 8 | +| main | false | 311 | 311 | ExprStmt | +| main | false | 315 | 315 | 0 | +| main | false | 316 | 316 | return ... | +| main | false | 318 | 318 | { ... } | +| main | true | 269 | 287 | | +| main | true | 271 | 316 | | +| main | true | 277 | 296 | | +| main | true | 282 | 307 | | +| main | true | 285 | 311 | | +| main | true | 287 | 285 | | +| main | true | 291 | 282 | | +| main | true | 296 | 299 | | +| main | true | 299 | 291 | | +| main | true | 300 | 271 | | +| main | true | 302 | 300 | | +| main | true | 307 | 310 | | +| main | true | 310 | 302 | | +| main | true | 311 | 277 | | +| main | true | 315 | 147 | | +| main | true | 316 | 315 | | +| main | true | 318 | 269 | | +| printf | false | 209 | 209 | printf | diff --git a/cpp/ql/test/library-tests/literals/uuidof/uuidof.cpp b/cpp/ql/test/library-tests/literals/uuidof/uuidof.cpp index afe3c0d0c95b..efc39fc0f285 100644 --- a/cpp/ql/test/library-tests/literals/uuidof/uuidof.cpp +++ b/cpp/ql/test/library-tests/literals/uuidof/uuidof.cpp @@ -15,5 +15,6 @@ void GetUUID() { uuid = __uuidof(Templ); S s; uuid = __uuidof(s); + uuid = __uuidof(0); } // semmle-extractor-options: --microsoft diff --git a/cpp/ql/test/library-tests/literals/uuidof/uuidof.expected b/cpp/ql/test/library-tests/literals/uuidof/uuidof.expected index a0635a81d4f4..b0fc3c38d8db 100644 --- a/cpp/ql/test/library-tests/literals/uuidof/uuidof.expected +++ b/cpp/ql/test/library-tests/literals/uuidof/uuidof.expected @@ -11,3 +11,4 @@ uuidofOperators | uuidof.cpp:14:12:14:30 | __uuidof(S) | const _GUID | 01234567-89ab-cdef-0123-456789abcdef | | uuidof.cpp:15:12:15:29 | __uuidof(S) | const _GUID | 01234567-89ab-cdef-0123-456789abcdef | | uuidof.cpp:17:12:17:22 | __uuidof(S) | const _GUID | 01234567-89ab-cdef-0123-456789abcdef | +| uuidof.cpp:18:12:18:22 | __uuidof(0) | const _GUID | 00000000-0000-0000-0000-000000000000 | diff --git a/cpp/ql/test/library-tests/locations/calls/calls.expected b/cpp/ql/test/library-tests/locations/calls/calls.expected index 0d47ee9aed8e..d17e8f5ac062 100644 --- a/cpp/ql/test/library-tests/locations/calls/calls.expected +++ b/cpp/ql/test/library-tests/locations/calls/calls.expected @@ -1,6 +1,6 @@ -| holder.cpp:13:2:13:20 | call to Holder | holder.cpp:5:2:5:7 | Holder | -| holder.cpp:17:14:17:15 | call to Holder | holder.cpp:5:2:5:7 | Holder | -| holder.cpp:18:18:18:31 | call to Holder | holder.cpp:5:2:5:7 | Holder | -| holder.cpp:19:2:19:3 | call to Holder | holder.cpp:6:2:6:7 | Holder | -| holder.cpp:19:5:19:5 | call to operator+ | holder.cpp:12:11:12:19 | operator+ | -| holder.cpp:19:7:19:8 | call to Holder | holder.cpp:6:2:6:7 | Holder | +| holder.cpp:13:2:13:20 | call to Holder | holder.cpp:5:2:5:2 | Holder | +| holder.cpp:17:14:17:15 | call to Holder | holder.cpp:5:2:5:2 | Holder | +| holder.cpp:18:18:18:31 | call to Holder | holder.cpp:5:2:5:2 | Holder | +| holder.cpp:19:2:19:3 | call to Holder | holder.cpp:6:2:6:2 | Holder | +| holder.cpp:19:5:19:5 | call to operator+ | holder.cpp:12:11:12:11 | operator+ | +| holder.cpp:19:7:19:8 | call to Holder | holder.cpp:6:2:6:2 | Holder | diff --git a/cpp/ql/test/library-tests/macros/inmacroexpansion/inmacroexpansion.expected b/cpp/ql/test/library-tests/macros/inmacroexpansion/inmacroexpansion.expected index 487f23c7baeb..580e3da83569 100644 --- a/cpp/ql/test/library-tests/macros/inmacroexpansion/inmacroexpansion.expected +++ b/cpp/ql/test/library-tests/macros/inmacroexpansion/inmacroexpansion.expected @@ -1,5 +1,8 @@ +| file://:0:0:0:0 | __va_list_tag | false | | file://:0:0:0:0 | operator= | false | | file://:0:0:0:0 | operator= | false | +| file://:0:0:0:0 | p#0 | false | +| file://:0:0:0:0 | p#0 | false | | test.cpp:0:0:0:0 | test.cpp | false | | test.cpp:2:1:2:61 | #define FOO class S{int i; void f(void) { int j; return; } }; | false | | test.cpp:4:1:4:1 | declaration of operator= | false | diff --git a/cpp/ql/test/library-tests/members/getters/members.expected b/cpp/ql/test/library-tests/members/getters/members.expected index ce7b7f6e56b0..a14ce851457c 100644 --- a/cpp/ql/test/library-tests/members/getters/members.expected +++ b/cpp/ql/test/library-tests/members/getters/members.expected @@ -1,24 +1,24 @@ | file://:0:0:0:0 | | test.cpp:34:6:34:6 | g | g() | Orphan | | test.cpp:2:7:2:7 | C | test.cpp:2:7:2:7 | operator= | C::operator=(C &&) | getAMember(), getAMember(5), getAMemberFunction(), getCanonicalMember(5), getDeclaringType() | | test.cpp:2:7:2:7 | C | test.cpp:2:7:2:7 | operator= | C::operator=(const C &) | getAMember(), getAMember(4), getAMemberFunction(), getCanonicalMember(4), getDeclaringType() | +| test.cpp:2:7:2:7 | C | test.cpp:5:10:5:10 | f_template_C | C::f_template_C(double) | getAMember(), getAMember(0), getAMemberFunction(), getDeclaringType() | +| test.cpp:2:7:2:7 | C | test.cpp:5:10:5:10 | f_template_C | C::f_template_C(int) | getAMember(), getAMember(0), getAMemberFunction(), getDeclaringType() | | test.cpp:2:7:2:7 | C | test.cpp:5:10:5:21 | f_template_C | C::f_template_C(T) | getAMember(), getAMember(0), getAMemberFunction(), getCanonicalMember(0), getDeclaringType() | -| test.cpp:2:7:2:7 | C | test.cpp:5:10:5:21 | f_template_C | C::f_template_C(double) | getAMember(), getAMember(0), getAMemberFunction(), getDeclaringType() | -| test.cpp:2:7:2:7 | C | test.cpp:5:10:5:21 | f_template_C | C::f_template_C(int) | getAMember(), getAMember(0), getAMemberFunction(), getDeclaringType() | +| test.cpp:2:7:2:7 | C | test.cpp:8:10:8:10 | f_template_C_D | C::f_template_C_D(int) | getAMember(), getAMember(1), getAMemberFunction(), getDeclaringType() | | test.cpp:2:7:2:7 | C | test.cpp:8:10:8:23 | f_template_C_D | C::f_template_C_D(T) | getAMember(), getAMember(1), getAMemberFunction(), getCanonicalMember(1), getDeclaringType() | -| test.cpp:2:7:2:7 | C | test.cpp:8:10:8:23 | f_template_C_D | C::f_template_C_D(int) | getAMember(), getAMember(1), getAMemberFunction(), getDeclaringType() | | test.cpp:2:7:2:7 | C | test.cpp:10:10:10:12 | f_C | C::f_C() | getAMember(), getAMember(2), getAMemberFunction(), getCanonicalMember(2), getDeclaringType() | | test.cpp:2:7:2:7 | C | test.cpp:11:10:11:14 | f_C_D | C::f_C_D() | getAMember(), getAMember(3), getAMemberFunction(), getCanonicalMember(3), getDeclaringType() | | test.cpp:14:7:14:7 | D | test.cpp:14:7:14:7 | operator= | D::operator=(D &&) | getAMember(), getAMember(5), getAMemberFunction(), getCanonicalMember(5), getDeclaringType() | | test.cpp:14:7:14:7 | D | test.cpp:14:7:14:7 | operator= | D::operator=(const D &) | getAMember(), getAMember(4), getAMemberFunction(), getCanonicalMember(4), getDeclaringType() | +| test.cpp:14:7:14:7 | D | test.cpp:17:10:17:10 | f_template_C_D | D::f_template_C_D(double) | getAMember(), getAMember(0), getAMemberFunction(), getDeclaringType() | | test.cpp:14:7:14:7 | D | test.cpp:17:10:17:23 | f_template_C_D | D::f_template_C_D(T) | getAMember(), getAMember(0), getAMemberFunction(), getCanonicalMember(0), getDeclaringType() | -| test.cpp:14:7:14:7 | D | test.cpp:17:10:17:23 | f_template_C_D | D::f_template_C_D(double) | getAMember(), getAMember(0), getAMemberFunction(), getDeclaringType() | +| test.cpp:14:7:14:7 | D | test.cpp:20:10:20:10 | f_template_D | D::f_template_D(double) | getAMember(), getAMember(1), getAMemberFunction(), getDeclaringType() | | test.cpp:14:7:14:7 | D | test.cpp:20:10:20:21 | f_template_D | D::f_template_D(T) | getAMember(), getAMember(1), getAMemberFunction(), getCanonicalMember(1), getDeclaringType() | -| test.cpp:14:7:14:7 | D | test.cpp:20:10:20:21 | f_template_D | D::f_template_D(double) | getAMember(), getAMember(1), getAMemberFunction(), getDeclaringType() | | test.cpp:14:7:14:7 | D | test.cpp:22:10:22:14 | f_C_D | D::f_C_D() | getAMember(), getAMember(2), getAMemberFunction(), getCanonicalMember(2), getDeclaringType() | | test.cpp:14:7:14:7 | D | test.cpp:23:10:23:12 | f_D | D::f_D() | getAMember(), getAMember(3), getAMemberFunction(), getCanonicalMember(3), getDeclaringType() | | test.cpp:27:7:27:7 | E | test.cpp:29:10:29:12 | f_E | E::f_E() | getAMember(), getAMember(0), getAMemberFunction(), getCanonicalMember(0), getDeclaringType() | | test.cpp:27:7:27:7 | E | test.cpp:31:10:31:16 | f_E_arg | E::f_E_arg(E) | getAMember(), getAMember(1), getAMemberFunction(), getCanonicalMember(1), getDeclaringType() | | test.cpp:27:7:27:7 | E | test.cpp:27:7:27:7 | operator= | E::operator=(E &&) | getAMember(), getAMember(3), getAMemberFunction(), getCanonicalMember(3), getDeclaringType() | | test.cpp:27:7:27:7 | E | test.cpp:27:7:27:7 | operator= | E::operator=(const E &) | getAMember(), getAMember(2), getAMemberFunction(), getCanonicalMember(2), getDeclaringType() | -| test.cpp:27:7:27:7 | E | test.cpp:29:10:29:12 | f_E | E::f_E() | getAMember(), getAMember(0), getAMemberFunction(), getCanonicalMember(0), getDeclaringType() | -| test.cpp:27:7:27:7 | E | test.cpp:31:10:31:16 | f_E_arg | E::f_E_arg(E) | getAMember(), getAMember(1), getAMemberFunction(), getCanonicalMember(1), getDeclaringType() | +| test.cpp:27:7:27:7 | E | test.cpp:29:10:29:10 | f_E | E::f_E() | getAMember(), getAMember(0), getAMemberFunction(), getCanonicalMember(0), getDeclaringType() | +| test.cpp:27:7:27:7 | E | test.cpp:31:10:31:10 | f_E_arg | E::f_E_arg(E) | getAMember(), getAMember(1), getAMemberFunction(), getCanonicalMember(1), getDeclaringType() | diff --git a/cpp/ql/test/library-tests/namespaces/namespaces/decls.expected b/cpp/ql/test/library-tests/namespaces/namespaces/decls.expected index 16f6b2bc855a..de75d8948681 100644 --- a/cpp/ql/test/library-tests/namespaces/namespaces/decls.expected +++ b/cpp/ql/test/library-tests/namespaces/namespaces/decls.expected @@ -4,6 +4,8 @@ | file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | operator= | __va_list_tag::operator= | false | | file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | operator= | __va_list_tag::operator= | false | | file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | overflow_arg_area | __va_list_tag::overflow_arg_area | false | +| file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | p#0 | | false | +| file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | p#0 | | false | | file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | reg_save_area | __va_list_tag::reg_save_area | false | | file://:0:0:0:0 | (global namespace) | namespaces.cpp:40:5:40:13 | globalInt | globalInt | true | | file://:0:0:0:0 | (global namespace) | namespaces.cpp:42:6:42:18 | globalIntUser | globalIntUser | true | diff --git a/cpp/ql/test/library-tests/namespaces/same_name/decls.expected b/cpp/ql/test/library-tests/namespaces/same_name/decls.expected index 3d72020f4c4b..8a6d70aee19f 100644 --- a/cpp/ql/test/library-tests/namespaces/same_name/decls.expected +++ b/cpp/ql/test/library-tests/namespaces/same_name/decls.expected @@ -4,6 +4,8 @@ | file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | operator= | | file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | operator= | | file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | overflow_arg_area | +| file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | (global namespace) | file://:0:0:0:0 | reg_save_area | | same_name.cpp:4:11:4:21 | namespace_a | same_name.cpp:2:11:2:11 | c | | same_name.cpp:4:11:4:21 | namespace_a | same_name.cpp:6:12:6:12 | c | diff --git a/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.expected b/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.expected index b837b9f9515c..9d77efe772f8 100644 --- a/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.expected +++ b/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.expected @@ -3,7 +3,6 @@ | copy_from_prototype.cpp:3:7:3:7 | operator= | a::operator=(a &&) -> a & | copy_from_prototype.cpp:3:7:3:7 | a | | | copy_from_prototype.cpp:3:7:3:7 | operator= | a::operator=(const a &) -> a & | copy_from_prototype.cpp:3:7:3:7 | a | | | copy_from_prototype.cpp:4:26:4:26 | a | a<>::a<(unnamed)>() -> void | copy_from_prototype.cpp:3:7:3:7 | a<> | 123 | -| copy_from_prototype.cpp:4:26:4:26 | a | a::a() -> void | copy_from_prototype.cpp:3:7:3:7 | a | | | copy_from_prototype.cpp:4:26:4:26 | a | a::a<(unnamed)>() -> void | copy_from_prototype.cpp:3:7:3:7 | a | | | copy_from_prototype.cpp:7:7:7:7 | b | b::b() -> void | copy_from_prototype.cpp:7:7:7:7 | b | | | copy_from_prototype.cpp:7:7:7:7 | b | b::b(b &&) -> void | copy_from_prototype.cpp:7:7:7:7 | b | | @@ -15,7 +14,6 @@ | copy_from_prototype.cpp:13:7:13:7 | operator= | c::operator=(c &&) -> c & | copy_from_prototype.cpp:13:7:13:7 | c | | | copy_from_prototype.cpp:13:7:13:7 | operator= | c::operator=(const c &) -> c & | copy_from_prototype.cpp:13:7:13:7 | c | | | copy_from_prototype.cpp:14:26:14:26 | c | c::c<(unnamed)>() -> void | copy_from_prototype.cpp:13:7:13:7 | c | Unknown literal | -| copy_from_prototype.cpp:14:26:14:26 | c | c::c() -> void | copy_from_prototype.cpp:13:7:13:7 | c | | | copy_from_prototype.cpp:14:26:14:26 | c | c::c<(unnamed)>() -> void | copy_from_prototype.cpp:13:7:13:7 | c | | | copy_from_prototype.cpp:17:7:17:7 | d | d::d() -> void | copy_from_prototype.cpp:17:7:17:7 | d | | | copy_from_prototype.cpp:17:7:17:7 | d | d::d(const d &) -> void | copy_from_prototype.cpp:17:7:17:7 | d | | @@ -28,5 +26,5 @@ | copy_from_prototype.cpp:22:8:22:8 | operator= | e::operator=(e &&) -> e & | copy_from_prototype.cpp:22:8:22:8 | e | | | copy_from_prototype.cpp:23:26:23:26 | e | e::e<(unnamed)>() -> void | copy_from_prototype.cpp:22:8:22:8 | e | 456 | | copy_from_prototype.cpp:26:35:26:43 | e | e::e<(unnamed)>() -> void | copy_from_prototype.cpp:22:8:22:8 | e | 456 | -| file://:0:0:0:0 | operator= | __va_list_tag::operator=() -> __va_list_tag & | file://:0:0:0:0 | __va_list_tag | | -| file://:0:0:0:0 | operator= | __va_list_tag::operator=() -> __va_list_tag & | file://:0:0:0:0 | __va_list_tag | | +| file://:0:0:0:0 | operator= | __va_list_tag::operator=(__va_list_tag &&) -> __va_list_tag & | file://:0:0:0:0 | __va_list_tag | | +| file://:0:0:0:0 | operator= | __va_list_tag::operator=(const __va_list_tag &) -> __va_list_tag & | file://:0:0:0:0 | __va_list_tag | | diff --git a/cpp/ql/test/library-tests/noexcept/noexcept/noexcept_specifier.expected b/cpp/ql/test/library-tests/noexcept/noexcept/noexcept_specifier.expected index 7d367b1674c6..344551c24aa4 100644 --- a/cpp/ql/test/library-tests/noexcept/noexcept/noexcept_specifier.expected +++ b/cpp/ql/test/library-tests/noexcept/noexcept/noexcept_specifier.expected @@ -15,7 +15,15 @@ | box.h:2:8:2:8 | definition of Box | no except | --- | | box.h:2:8:2:8 | definition of Box | no except | --- | | box.h:2:8:2:8 | definition of Box | no except | --- | +| box.h:3:3:3:3 | definition of Box | -------- | __has_nothrow_copy | +| box.h:3:3:3:3 | definition of Box | -------- | __has_nothrow_copy | +| box.h:3:3:3:3 | definition of Box | -------- | __has_nothrow_copy | +| box.h:3:3:3:3 | definition of Box | -------- | __has_nothrow_copy | | box.h:3:3:3:5 | definition of Box | -------- | __has_nothrow_copy | +| box.h:9:15:9:15 | definition of box | -------- | --- | +| box.h:9:15:9:15 | definition of box | -------- | --- | +| box.h:9:15:9:15 | definition of box | -------- | --- | +| box.h:9:15:9:15 | definition of box | -------- | --- | | box.h:9:15:9:17 | definition of box | -------- | --- | | noexcept.cpp:2:7:2:7 | declaration of operator= | -------- | --- | | noexcept.cpp:2:7:2:7 | declaration of operator= | -------- | --- | diff --git a/cpp/ql/test/library-tests/numlines/numlines.cpp b/cpp/ql/test/library-tests/numlines/numlines.cpp index b91453f14870..bb4991f9a23d 100644 --- a/cpp/ql/test/library-tests/numlines/numlines.cpp +++ b/cpp/ql/test/library-tests/numlines/numlines.cpp @@ -48,3 +48,26 @@ void templateFunctionUser(signed int x, unsigned int y) { twiceUsedTemplateFunction(y); } +class C { + public: + int ff() { + int i; + gg(i); + } + // Although there is a declaration of gg starting here, + // the number of lines count shouldn't. + template + void gg(T t); +}; + + + +template +void C::gg(T t) { + ; +} +void hh() { + C n; + n.ff(); +} + diff --git a/cpp/ql/test/library-tests/numlines/numlines.expected b/cpp/ql/test/library-tests/numlines/numlines.expected index a04cd4c34f9e..3feca61fd693 100644 --- a/cpp/ql/test/library-tests/numlines/numlines.expected +++ b/cpp/ql/test/library-tests/numlines/numlines.expected @@ -1,8 +1,11 @@ +| C::ff() | 4 | 4 | 0 | +| C::gg(T) | 4 | 4 | 0 | | conventional() | 4 | 4 | 1 | +| hh() | 4 | 4 | 0 | | long_char() | 3 | 3 | 0 | | long_string() | 5 | 5 | 0 | | misleading_comment() | 7 | 2 | 5 | -| numlines | 50 | 37 | 6 | +| numlines | 73 | 54 | 8 | | onceUsedTemplateFunction(T) | 6 | 6 | 0 | | templateFunctionUser(signed int,unsigned int) | 5 | 5 | 0 | | twiceUsedTemplateFunction(T) | 6 | 6 | 0 | diff --git a/cpp/ql/test/library-tests/parameters/parameters/Parameters1.expected b/cpp/ql/test/library-tests/parameters/parameters/Parameters1.expected index bb7a28a21b7d..5001757b461d 100644 --- a/cpp/ql/test/library-tests/parameters/parameters/Parameters1.expected +++ b/cpp/ql/test/library-tests/parameters/parameters/Parameters1.expected @@ -1,5 +1,5 @@ -| file://:0:0:0:0 | operator= | 0 | -| file://:0:0:0:0 | operator= | 0 | +| file://:0:0:0:0 | operator= | 1 | +| file://:0:0:0:0 | operator= | 1 | | parameters.cpp:3:5:3:14 | Callback_1 | 2 | | parameters.cpp:8:5:8:14 | Callback_2 | 2 | | parameters.cpp:13:5:13:14 | Callback_3 | 2 | diff --git a/cpp/ql/test/library-tests/pod/isPOD03.expected b/cpp/ql/test/library-tests/pod/isPOD03.expected index b39b5878de33..298c4c43492f 100644 --- a/cpp/ql/test/library-tests/pod/isPOD03.expected +++ b/cpp/ql/test/library-tests/pod/isPOD03.expected @@ -1,4 +1,4 @@ -| file://:0:0:0:0 | __va_list_tag | true | +| file://:0:0:0:0 | __va_list_tag | false | | test1.cpp:5:8:5:8 | S | false | | test2.cpp:6:8:6:10 | Foo | true | | test3.cpp:2:7:2:14 | MyClass1 | true | diff --git a/cpp/ql/test/library-tests/pointsto/basic/sets.expected b/cpp/ql/test/library-tests/pointsto/basic/sets.expected index e8f6b7169dad..94f869bd29be 100644 --- a/cpp/ql/test/library-tests/pointsto/basic/sets.expected +++ b/cpp/ql/test/library-tests/pointsto/basic/sets.expected @@ -3,167 +3,173 @@ | pointsto | false | 2 | 2 | set: {overflow_arg_area} | | pointsto | false | 3 | 3 | set: {reg_save_area} | | pointsto | false | 4 | 4 | set: {operator=} | -| pointsto | false | 5 | 5 | set: {operator=} | -| pointsto | false | 6 | 6 | set: {MyStruct} | -| pointsto | false | 7 | 7 | set: {data} | -| pointsto | false | 8 | 8 | set: {operator=} | -| pointsto | false | 9 | 9 | set: {p#0} | -| pointsto | false | 10 | 10 | set: {operator=} | -| pointsto | false | 11 | 11 | set: {p#0} | -| pointsto | false | 12 | 12 | set: {(unsigned long)..., 10} | -| pointsto | false | 13 | 13 | set: {use} | -| pointsto | false | 14 | 14 | set: {v} | -| pointsto | false | 15 | 15 | set: {test} | -| pointsto | false | 16 | 16 | set: {cond} | -| pointsto | false | 17 | 17 | set: {(bool)..., cond} | -| pointsto | false | 18 | 18 | set: {p1, p1, p1} | -| pointsto | false | 19 | 19 | set: {a, a} | -| pointsto | false | 20 | 20 | set: {b, b} | -| pointsto | false | 21 | 21 | set: {p2, p2} | -| pointsto | false | 22 | 22 | set: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} | -| pointsto | false | 23 | 23 | set: {c, c} | -| pointsto | false | 24 | 24 | set: {pp1, pp1} | -| pointsto | false | 25 | 25 | set: {d, d} | -| pointsto | false | 26 | 26 | set: {pp2, pp2} | -| pointsto | false | 27 | 27 | set: {a, b, c, d} | -| pointsto | false | 28 | 28 | set: {& ..., & ..., ... = ..., ... = ..., p3} | -| pointsto | false | 29 | 29 | set: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} | -| pointsto | false | 79 | 79 | file://:0:0:0:0\ngp_offset | -| pointsto | false | 80 | 80 | file://:0:0:0:0\nfp_offset | -| pointsto | false | 81 | 81 | file://:0:0:0:0\noverflow_arg_area | -| pointsto | false | 82 | 82 | file://:0:0:0:0\nreg_save_area | -| pointsto | false | 83 | 83 | \noperator= | -| pointsto | false | 85 | 85 | \noperator= | -| pointsto | false | 88 | 88 | test.cpp:2:16:2:23\nMyStruct | -| pointsto | false | 89 | 89 | test.cpp:3:6:3:9\ndata | -| pointsto | false | 91 | 91 | test.cpp:2:16:2:16\noperator= | -| pointsto | false | 92 | 92 | file://:0:0:0:0\np#0 | -| pointsto | false | 95 | 95 | test.cpp:2:16:2:16\noperator= | -| pointsto | false | 96 | 96 | file://:0:0:0:0\np#0 | -| pointsto | false | 99 | 99 | test.cpp:3:11:3:12\n10 | -| pointsto | false | 100 | 100 | test.cpp:3:11:3:12\n(unsigned long)... | -| pointsto | false | 104 | 104 | test.cpp:6:10:6:10\na | -| pointsto | false | 106 | 106 | test.cpp:6:13:6:13\nb | -| pointsto | false | 108 | 108 | test.cpp:6:16:6:16\nc | -| pointsto | false | 110 | 110 | test.cpp:6:19:6:19\nd | -| pointsto | false | 112 | 112 | test.cpp:7:11:7:12\np1 | -| pointsto | false | 116 | 116 | test.cpp:7:16:7:17\np2 | -| pointsto | false | 118 | 118 | test.cpp:7:21:7:22\np3 | -| pointsto | false | 120 | 120 | test.cpp:8:12:8:14\npp1 | -| pointsto | false | 124 | 124 | test.cpp:8:19:8:21\npp2 | -| pointsto | false | 126 | 126 | test.cpp:10:6:10:8\nuse | -| pointsto | false | 127 | 127 | test.cpp:10:19:10:19\nv | -| pointsto | false | 130 | 130 | test.cpp:12:6:12:9\ntest | -| pointsto | false | 131 | 131 | test.cpp:12:15:12:18\ncond | -| pointsto | false | 132 | 132 | test.cpp:14:6:14:9\ncond | -| pointsto | false | 133 | 133 | test.cpp:14:6:14:9\n(bool)... | -| pointsto | false | 134 | 134 | test.cpp:16:3:16:4\np1 | -| pointsto | false | 135 | 135 | test.cpp:16:9:16:9\na | -| pointsto | false | 136 | 136 | test.cpp:16:8:16:9\n& ... | -| pointsto | false | 137 | 137 | test.cpp:16:3:16:9\n... = ... | -| pointsto | false | 140 | 140 | test.cpp:18:3:18:4\np1 | -| pointsto | false | 141 | 141 | test.cpp:18:9:18:9\nb | -| pointsto | false | 142 | 142 | test.cpp:18:8:18:9\n& ... | -| pointsto | false | 143 | 143 | test.cpp:18:3:18:9\n... = ... | -| pointsto | false | 148 | 148 | test.cpp:20:2:20:3\np2 | -| pointsto | false | 149 | 149 | test.cpp:20:7:20:8\np1 | -| pointsto | false | 150 | 150 | test.cpp:20:2:20:8\n... = ... | -| pointsto | false | 152 | 152 | test.cpp:22:2:22:3\np3 | -| pointsto | false | 153 | 153 | test.cpp:22:8:22:8\nc | -| pointsto | false | 154 | 154 | test.cpp:22:7:22:8\n& ... | -| pointsto | false | 155 | 155 | test.cpp:22:2:22:8\n... = ... | -| pointsto | false | 157 | 157 | test.cpp:23:2:23:4\npp1 | -| pointsto | false | 158 | 158 | test.cpp:23:9:23:10\np3 | -| pointsto | false | 159 | 159 | test.cpp:23:8:23:10\n& ... | -| pointsto | false | 160 | 160 | test.cpp:23:2:23:10\n... = ... | -| pointsto | false | 162 | 162 | test.cpp:24:2:24:3\np3 | -| pointsto | false | 163 | 163 | test.cpp:24:8:24:8\nd | -| pointsto | false | 164 | 164 | test.cpp:24:7:24:8\n& ... | -| pointsto | false | 165 | 165 | test.cpp:24:2:24:8\n... = ... | -| pointsto | false | 168 | 168 | test.cpp:25:2:25:4\npp2 | -| pointsto | false | 169 | 169 | test.cpp:25:9:25:10\np3 | -| pointsto | false | 170 | 170 | test.cpp:25:8:25:10\n& ... | -| pointsto | false | 171 | 171 | test.cpp:25:2:25:10\n... = ... | -| pointsto | false | 176 | 176 | test.cpp:27:6:27:6\na | -| pointsto | false | 177 | 177 | test.cpp:27:9:27:9\nb | -| pointsto | false | 178 | 178 | test.cpp:27:12:27:12\nc | -| pointsto | false | 179 | 179 | test.cpp:27:15:27:15\nd | -| pointsto | false | 180 | 180 | test.cpp:27:18:27:19\np1 | -| pointsto | false | 181 | 181 | test.cpp:27:22:27:23\np2 | -| pointsto | false | 182 | 182 | test.cpp:27:26:27:27\np3 | -| pointsto | false | 183 | 183 | test.cpp:27:30:27:32\npp1 | -| pointsto | false | 184 | 184 | test.cpp:27:35:27:37\npp2 | -| pointsto | true | 0 | 79 | pt: {gp_offset} -> gp_offset | -| pointsto | true | 1 | 80 | pt: {fp_offset} -> fp_offset | -| pointsto | true | 2 | 81 | pt: {overflow_arg_area} -> overflow_arg_area | -| pointsto | true | 3 | 82 | pt: {reg_save_area} -> reg_save_area | -| pointsto | true | 4 | 83 | pt: {operator=} -> operator= | -| pointsto | true | 5 | 85 | pt: {operator=} -> operator= | -| pointsto | true | 6 | 88 | pt: {MyStruct} -> MyStruct | -| pointsto | true | 7 | 89 | pt: {data} -> data | -| pointsto | true | 8 | 91 | pt: {operator=} -> operator= | -| pointsto | true | 9 | 92 | pt: {p#0} -> p#0 | -| pointsto | true | 10 | 95 | pt: {operator=} -> operator= | -| pointsto | true | 11 | 96 | pt: {p#0} -> p#0 | -| pointsto | true | 12 | 99 | pt: {(unsigned long)..., 10} -> 10 | -| pointsto | true | 12 | 100 | pt: {(unsigned long)..., 10} -> (unsigned long)... | -| pointsto | true | 13 | 126 | pt: {use} -> use | -| pointsto | true | 14 | 6 | sf | -| pointsto | true | 14 | 127 | pt: {v} -> v | -| pointsto | true | 15 | 130 | pt: {test} -> test | -| pointsto | true | 16 | 131 | pt: {cond} -> cond | -| pointsto | true | 17 | 132 | pt: {(bool)..., cond} -> cond | -| pointsto | true | 17 | 133 | pt: {(bool)..., cond} -> (bool)... | -| pointsto | true | 18 | 112 | pt: {p1, p1, p1} -> p1 | -| pointsto | true | 18 | 134 | pt: {p1, p1, p1} -> p1 | -| pointsto | true | 18 | 140 | pt: {p1, p1, p1} -> p1 | -| pointsto | true | 19 | 6 | sf | -| pointsto | true | 19 | 22 | sf | -| pointsto | true | 19 | 104 | pt: {a, a} -> a | -| pointsto | true | 19 | 135 | pt: {a, a} -> a | -| pointsto | true | 20 | 6 | sf | -| pointsto | true | 20 | 22 | sf | -| pointsto | true | 20 | 106 | pt: {b, b} -> b | -| pointsto | true | 20 | 141 | pt: {b, b} -> b | -| pointsto | true | 21 | 116 | pt: {p2, p2} -> p2 | -| pointsto | true | 21 | 148 | pt: {p2, p2} -> p2 | -| pointsto | true | 22 | 136 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> & ... | -| pointsto | true | 22 | 137 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> ... = ... | -| pointsto | true | 22 | 142 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> & ... | -| pointsto | true | 22 | 143 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> ... = ... | -| pointsto | true | 22 | 149 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> p1 | -| pointsto | true | 22 | 150 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> ... = ... | -| pointsto | true | 22 | 180 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> p1 | -| pointsto | true | 22 | 181 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> p2 | -| pointsto | true | 23 | 6 | sf | -| pointsto | true | 23 | 28 | sf | -| pointsto | true | 23 | 108 | pt: {c, c} -> c | -| pointsto | true | 23 | 153 | pt: {c, c} -> c | -| pointsto | true | 24 | 120 | pt: {pp1, pp1} -> pp1 | -| pointsto | true | 24 | 157 | pt: {pp1, pp1} -> pp1 | -| pointsto | true | 25 | 6 | sf | -| pointsto | true | 25 | 28 | sf | -| pointsto | true | 25 | 110 | pt: {d, d} -> d | -| pointsto | true | 25 | 163 | pt: {d, d} -> d | -| pointsto | true | 26 | 124 | pt: {pp2, pp2} -> pp2 | -| pointsto | true | 26 | 168 | pt: {pp2, pp2} -> pp2 | -| pointsto | true | 27 | 176 | pt: {a, b, c, d} -> a | -| pointsto | true | 27 | 177 | pt: {a, b, c, d} -> b | -| pointsto | true | 27 | 178 | pt: {a, b, c, d} -> c | -| pointsto | true | 27 | 179 | pt: {a, b, c, d} -> d | -| pointsto | true | 28 | 154 | pt: {& ..., & ..., ... = ..., ... = ..., p3} -> & ... | -| pointsto | true | 28 | 155 | pt: {& ..., & ..., ... = ..., ... = ..., p3} -> ... = ... | -| pointsto | true | 28 | 164 | pt: {& ..., & ..., ... = ..., ... = ..., p3} -> & ... | -| pointsto | true | 28 | 165 | pt: {& ..., & ..., ... = ..., ... = ..., p3} -> ... = ... | -| pointsto | true | 28 | 182 | pt: {& ..., & ..., ... = ..., ... = ..., p3} -> p3 | -| pointsto | true | 29 | 118 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> p3 | -| pointsto | true | 29 | 152 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> p3 | -| pointsto | true | 29 | 158 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> p3 | -| pointsto | true | 29 | 159 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> & ... | -| pointsto | true | 29 | 160 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> ... = ... | -| pointsto | true | 29 | 162 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> p3 | -| pointsto | true | 29 | 169 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> p3 | -| pointsto | true | 29 | 170 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> & ... | -| pointsto | true | 29 | 171 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> ... = ... | -| pointsto | true | 29 | 183 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> pp1 | -| pointsto | true | 29 | 184 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> pp2 | +| pointsto | false | 5 | 5 | set: {p#0} | +| pointsto | false | 6 | 6 | set: {operator=} | +| pointsto | false | 7 | 7 | set: {p#0} | +| pointsto | false | 8 | 8 | set: {MyStruct} | +| pointsto | false | 9 | 9 | set: {test} | +| pointsto | false | 10 | 10 | set: {cond} | +| pointsto | false | 11 | 11 | set: {(bool)..., cond} | +| pointsto | false | 12 | 12 | set: {p1, p1, p1} | +| pointsto | false | 13 | 13 | set: {a, a} | +| pointsto | false | 14 | 14 | set: {b, b} | +| pointsto | false | 15 | 15 | set: {p2, p2} | +| pointsto | false | 16 | 16 | set: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} | +| pointsto | false | 17 | 17 | set: {c, c} | +| pointsto | false | 18 | 18 | set: {pp1, pp1} | +| pointsto | false | 19 | 19 | set: {d, d} | +| pointsto | false | 20 | 20 | set: {pp2, pp2} | +| pointsto | false | 21 | 21 | set: {use} | +| pointsto | false | 22 | 22 | set: {a, b, c, d} | +| pointsto | false | 23 | 23 | set: {& ..., & ..., ... = ..., ... = ..., p3} | +| pointsto | false | 24 | 24 | set: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} | +| pointsto | false | 25 | 25 | set: {v} | +| pointsto | false | 26 | 26 | set: {operator=} | +| pointsto | false | 27 | 27 | set: {p#0} | +| pointsto | false | 28 | 28 | set: {operator=} | +| pointsto | false | 29 | 29 | set: {p#0} | +| pointsto | false | 30 | 30 | set: {data} | +| pointsto | false | 31 | 31 | set: {(unsigned long)..., 10} | +| pointsto | false | 94 | 94 | file://:0:0:0:0\ngp_offset | +| pointsto | false | 96 | 96 | file://:0:0:0:0\nfp_offset | +| pointsto | false | 98 | 98 | file://:0:0:0:0\noverflow_arg_area | +| pointsto | false | 101 | 101 | file://:0:0:0:0\nreg_save_area | +| pointsto | false | 104 | 104 | \noperator= | +| pointsto | false | 105 | 105 | \np#0 | +| pointsto | false | 109 | 109 | \noperator= | +| pointsto | false | 110 | 110 | \np#0 | +| pointsto | false | 156 | 156 | test.cpp:2:16:2:23\nMyStruct | +| pointsto | false | 161 | 161 | test.cpp:12:6:12:9\ntest | +| pointsto | false | 162 | 162 | test.cpp:12:15:12:18\ncond | +| pointsto | false | 165 | 165 | test.cpp:14:6:14:9\ncond | +| pointsto | false | 166 | 166 | test.cpp:14:6:14:9\n(bool)... | +| pointsto | false | 169 | 169 | test.cpp:7:11:7:12\np1 | +| pointsto | false | 170 | 170 | test.cpp:16:3:16:4\np1 | +| pointsto | false | 171 | 171 | test.cpp:6:10:6:10\na | +| pointsto | false | 172 | 172 | test.cpp:16:9:16:9\na | +| pointsto | false | 173 | 173 | test.cpp:16:8:16:9\n& ... | +| pointsto | false | 174 | 174 | test.cpp:16:3:16:9\n... = ... | +| pointsto | false | 177 | 177 | test.cpp:18:3:18:4\np1 | +| pointsto | false | 178 | 178 | test.cpp:6:13:6:13\nb | +| pointsto | false | 179 | 179 | test.cpp:18:9:18:9\nb | +| pointsto | false | 180 | 180 | test.cpp:18:8:18:9\n& ... | +| pointsto | false | 181 | 181 | test.cpp:18:3:18:9\n... = ... | +| pointsto | false | 185 | 185 | test.cpp:7:16:7:17\np2 | +| pointsto | false | 186 | 186 | test.cpp:20:2:20:3\np2 | +| pointsto | false | 187 | 187 | test.cpp:20:7:20:8\np1 | +| pointsto | false | 188 | 188 | test.cpp:20:2:20:8\n... = ... | +| pointsto | false | 190 | 190 | test.cpp:7:21:7:22\np3 | +| pointsto | false | 191 | 191 | test.cpp:22:2:22:3\np3 | +| pointsto | false | 192 | 192 | test.cpp:6:16:6:16\nc | +| pointsto | false | 193 | 193 | test.cpp:22:8:22:8\nc | +| pointsto | false | 194 | 194 | test.cpp:22:7:22:8\n& ... | +| pointsto | false | 195 | 195 | test.cpp:22:2:22:8\n... = ... | +| pointsto | false | 199 | 199 | test.cpp:8:12:8:14\npp1 | +| pointsto | false | 200 | 200 | test.cpp:23:2:23:4\npp1 | +| pointsto | false | 201 | 201 | test.cpp:23:9:23:10\np3 | +| pointsto | false | 202 | 202 | test.cpp:23:8:23:10\n& ... | +| pointsto | false | 203 | 203 | test.cpp:23:2:23:10\n... = ... | +| pointsto | false | 205 | 205 | test.cpp:24:2:24:3\np3 | +| pointsto | false | 206 | 206 | test.cpp:6:19:6:19\nd | +| pointsto | false | 207 | 207 | test.cpp:24:8:24:8\nd | +| pointsto | false | 208 | 208 | test.cpp:24:7:24:8\n& ... | +| pointsto | false | 209 | 209 | test.cpp:24:2:24:8\n... = ... | +| pointsto | false | 211 | 211 | test.cpp:8:19:8:21\npp2 | +| pointsto | false | 212 | 212 | test.cpp:25:2:25:4\npp2 | +| pointsto | false | 213 | 213 | test.cpp:25:9:25:10\np3 | +| pointsto | false | 214 | 214 | test.cpp:25:8:25:10\n& ... | +| pointsto | false | 215 | 215 | test.cpp:25:2:25:10\n... = ... | +| pointsto | false | 217 | 217 | test.cpp:10:6:10:8\nuse | +| pointsto | false | 221 | 221 | test.cpp:27:6:27:6\na | +| pointsto | false | 222 | 222 | test.cpp:27:9:27:9\nb | +| pointsto | false | 223 | 223 | test.cpp:27:12:27:12\nc | +| pointsto | false | 224 | 224 | test.cpp:27:15:27:15\nd | +| pointsto | false | 225 | 225 | test.cpp:27:18:27:19\np1 | +| pointsto | false | 226 | 226 | test.cpp:27:22:27:23\np2 | +| pointsto | false | 227 | 227 | test.cpp:27:26:27:27\np3 | +| pointsto | false | 228 | 228 | test.cpp:27:30:27:32\npp1 | +| pointsto | false | 229 | 229 | test.cpp:27:35:27:37\npp2 | +| pointsto | false | 233 | 233 | test.cpp:10:19:10:19\nv | +| pointsto | false | 235 | 235 | test.cpp:2:16:2:16\noperator= | +| pointsto | false | 237 | 237 | file://:0:0:0:0\np#0 | +| pointsto | false | 242 | 242 | test.cpp:2:16:2:16\noperator= | +| pointsto | false | 243 | 243 | file://:0:0:0:0\np#0 | +| pointsto | false | 247 | 247 | test.cpp:3:6:3:9\ndata | +| pointsto | false | 250 | 250 | test.cpp:3:11:3:12\n10 | +| pointsto | false | 251 | 251 | test.cpp:3:11:3:12\n(unsigned long)... | +| pointsto | true | 0 | 94 | pt: {gp_offset} -> gp_offset | +| pointsto | true | 1 | 96 | pt: {fp_offset} -> fp_offset | +| pointsto | true | 2 | 98 | pt: {overflow_arg_area} -> overflow_arg_area | +| pointsto | true | 3 | 101 | pt: {reg_save_area} -> reg_save_area | +| pointsto | true | 4 | 104 | pt: {operator=} -> operator= | +| pointsto | true | 5 | 105 | pt: {p#0} -> p#0 | +| pointsto | true | 6 | 109 | pt: {operator=} -> operator= | +| pointsto | true | 7 | 110 | pt: {p#0} -> p#0 | +| pointsto | true | 8 | 156 | pt: {MyStruct} -> MyStruct | +| pointsto | true | 9 | 161 | pt: {test} -> test | +| pointsto | true | 10 | 162 | pt: {cond} -> cond | +| pointsto | true | 11 | 165 | pt: {(bool)..., cond} -> cond | +| pointsto | true | 11 | 166 | pt: {(bool)..., cond} -> (bool)... | +| pointsto | true | 12 | 169 | pt: {p1, p1, p1} -> p1 | +| pointsto | true | 12 | 170 | pt: {p1, p1, p1} -> p1 | +| pointsto | true | 12 | 177 | pt: {p1, p1, p1} -> p1 | +| pointsto | true | 13 | 8 | sf | +| pointsto | true | 13 | 16 | sf | +| pointsto | true | 13 | 171 | pt: {a, a} -> a | +| pointsto | true | 13 | 172 | pt: {a, a} -> a | +| pointsto | true | 14 | 8 | sf | +| pointsto | true | 14 | 16 | sf | +| pointsto | true | 14 | 178 | pt: {b, b} -> b | +| pointsto | true | 14 | 179 | pt: {b, b} -> b | +| pointsto | true | 15 | 185 | pt: {p2, p2} -> p2 | +| pointsto | true | 15 | 186 | pt: {p2, p2} -> p2 | +| pointsto | true | 16 | 173 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> & ... | +| pointsto | true | 16 | 174 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> ... = ... | +| pointsto | true | 16 | 180 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> & ... | +| pointsto | true | 16 | 181 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> ... = ... | +| pointsto | true | 16 | 187 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> p1 | +| pointsto | true | 16 | 188 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> ... = ... | +| pointsto | true | 16 | 225 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> p1 | +| pointsto | true | 16 | 226 | pt: {& ..., & ..., ... = ..., ... = ..., ... = ..., p1, p1, p2} -> p2 | +| pointsto | true | 17 | 8 | sf | +| pointsto | true | 17 | 23 | sf | +| pointsto | true | 17 | 192 | pt: {c, c} -> c | +| pointsto | true | 17 | 193 | pt: {c, c} -> c | +| pointsto | true | 18 | 199 | pt: {pp1, pp1} -> pp1 | +| pointsto | true | 18 | 200 | pt: {pp1, pp1} -> pp1 | +| pointsto | true | 19 | 8 | sf | +| pointsto | true | 19 | 23 | sf | +| pointsto | true | 19 | 206 | pt: {d, d} -> d | +| pointsto | true | 19 | 207 | pt: {d, d} -> d | +| pointsto | true | 20 | 211 | pt: {pp2, pp2} -> pp2 | +| pointsto | true | 20 | 212 | pt: {pp2, pp2} -> pp2 | +| pointsto | true | 21 | 217 | pt: {use} -> use | +| pointsto | true | 22 | 221 | pt: {a, b, c, d} -> a | +| pointsto | true | 22 | 222 | pt: {a, b, c, d} -> b | +| pointsto | true | 22 | 223 | pt: {a, b, c, d} -> c | +| pointsto | true | 22 | 224 | pt: {a, b, c, d} -> d | +| pointsto | true | 23 | 194 | pt: {& ..., & ..., ... = ..., ... = ..., p3} -> & ... | +| pointsto | true | 23 | 195 | pt: {& ..., & ..., ... = ..., ... = ..., p3} -> ... = ... | +| pointsto | true | 23 | 208 | pt: {& ..., & ..., ... = ..., ... = ..., p3} -> & ... | +| pointsto | true | 23 | 209 | pt: {& ..., & ..., ... = ..., ... = ..., p3} -> ... = ... | +| pointsto | true | 23 | 227 | pt: {& ..., & ..., ... = ..., ... = ..., p3} -> p3 | +| pointsto | true | 24 | 190 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> p3 | +| pointsto | true | 24 | 191 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> p3 | +| pointsto | true | 24 | 201 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> p3 | +| pointsto | true | 24 | 202 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> & ... | +| pointsto | true | 24 | 203 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> ... = ... | +| pointsto | true | 24 | 205 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> p3 | +| pointsto | true | 24 | 213 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> p3 | +| pointsto | true | 24 | 214 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> & ... | +| pointsto | true | 24 | 215 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> ... = ... | +| pointsto | true | 24 | 228 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> pp1 | +| pointsto | true | 24 | 229 | pt: {& ..., & ..., ... = ..., ... = ..., p3, p3, p3, p3, p3, pp1, pp2} -> pp2 | +| pointsto | true | 25 | 8 | sf | +| pointsto | true | 25 | 233 | pt: {v} -> v | +| pointsto | true | 26 | 235 | pt: {operator=} -> operator= | +| pointsto | true | 27 | 237 | pt: {p#0} -> p#0 | +| pointsto | true | 28 | 242 | pt: {operator=} -> operator= | +| pointsto | true | 29 | 243 | pt: {p#0} -> p#0 | +| pointsto | true | 30 | 247 | pt: {data} -> data | +| pointsto | true | 31 | 250 | pt: {(unsigned long)..., 10} -> 10 | +| pointsto | true | 31 | 251 | pt: {(unsigned long)..., 10} -> (unsigned long)... | diff --git a/cpp/ql/test/library-tests/scopes/parents/parents.expected b/cpp/ql/test/library-tests/scopes/parents/parents.expected index 523d0884393b..2c4d9f1ad3d3 100644 --- a/cpp/ql/test/library-tests/scopes/parents/parents.expected +++ b/cpp/ql/test/library-tests/scopes/parents/parents.expected @@ -6,6 +6,8 @@ | 1 | file://:0:0:0:0 | __va_list_tag | file://:0:0:0:0 | operator= | | 1 | file://:0:0:0:0 | __va_list_tag | file://:0:0:0:0 | overflow_arg_area | | 1 | file://:0:0:0:0 | __va_list_tag | file://:0:0:0:0 | reg_save_area | +| 1 | file://:0:0:0:0 | operator= | file://:0:0:0:0 | p#0 | +| 1 | file://:0:0:0:0 | operator= | file://:0:0:0:0 | p#0 | | 1 | parents.cpp:2:11:2:13 | foo | parents.cpp:3:13:3:15 | foo::bar | | 1 | parents.cpp:3:13:3:15 | foo::bar | parents.cpp:4:10:4:10 | f | | 1 | parents.cpp:4:10:4:10 | f | parents.cpp:4:16:4:16 | i | diff --git a/cpp/ql/test/library-tests/sideEffects/functions/sideEffects.expected b/cpp/ql/test/library-tests/sideEffects/functions/sideEffects.expected index ae4c8d3fb3e1..44888d8f6a04 100644 --- a/cpp/ql/test/library-tests/sideEffects/functions/sideEffects.expected +++ b/cpp/ql/test/library-tests/sideEffects/functions/sideEffects.expected @@ -43,8 +43,8 @@ | cpp.cpp:87:5:87:26 | functionAccessesStatic | int | false | | cpp.cpp:93:6:93:14 | increment | int & -> void | false | | cpp.cpp:97:6:97:16 | doIncrement | void | false | -| file://:0:0:0:0 | operator= | __va_list_tag & | false | -| file://:0:0:0:0 | operator= | __va_list_tag & | false | +| file://:0:0:0:0 | operator= | __va_list_tag && -> __va_list_tag & | false | +| file://:0:0:0:0 | operator= | const __va_list_tag & -> __va_list_tag & | false | | sideEffects.c:4:5:4:6 | f1 | int | true | | sideEffects.c:12:5:12:6 | f2 | int | true | | sideEffects.c:20:5:20:6 | f3 | int | true | diff --git a/cpp/ql/test/library-tests/special_members/detect/detect.expected b/cpp/ql/test/library-tests/special_members/detect/detect.expected index 1d4f114a4990..9b89a3efad48 100644 --- a/cpp/ql/test/library-tests/special_members/detect/detect.expected +++ b/cpp/ql/test/library-tests/special_members/detect/detect.expected @@ -21,11 +21,12 @@ | 4 copy assignment | C::operator=(Cinside_lref c) | | 4 copy assignment | C::operator=(const volatile C & c) | | 4 copy assignment | C::operator=(rref c_copy) | +| 4 copy assignment | __va_list_tag::operator=(const __va_list_tag & p#0) | | 5 move assignment | C::operator=(Ctop_rref c) | | 5 move assignment | C::operator=(const volatile C && c) | | 5 move assignment | C::operator=(rref c_move) | +| 5 move assignment | __va_list_tag::operator=(__va_list_tag && p#0) | | 6 none of the above | C::operator=(int i) | | 6 none of the above | C::operator=(volatile C & c_templated) | | 6 none of the above | C::operator=(volatile C && c_templated) | | 6 none of the above | C::operator==(const C & c) | -| 6 none of the above | __va_list_tag::operator=() | diff --git a/cpp/ql/test/library-tests/std_layout/test.c b/cpp/ql/test/library-tests/std_layout/test.c new file mode 100644 index 000000000000..abd7556697c3 --- /dev/null +++ b/cpp/ql/test/library-tests/std_layout/test.c @@ -0,0 +1,5 @@ + +// Confirm that `Class::isStandardLayout()` holds for a C struct. +struct PlainOldCStruct { + int x; +}; diff --git a/cpp/ql/test/library-tests/std_layout/test.cpp b/cpp/ql/test/library-tests/std_layout/test.cpp new file mode 100644 index 000000000000..10180749bb8b --- /dev/null +++ b/cpp/ql/test/library-tests/std_layout/test.cpp @@ -0,0 +1,89 @@ + +// AStd is a standard layout type +struct AStd { + int x; +}; + +// BNonStd is NOT a standard layout type - not all members have the same access +// control +class BNonStd { + int x; +public: + int y; +}; + +// CNonStd is NOT a standard layout type - it has a virtual function +class CNonStd { + virtual void f(); +}; + +// DNonStd is NOT a standard layout type - it has a virtual base class +class DNonStd : public virtual AStd {}; + +// ENonStd is NOT a standard layout type - it has a data member of reference +// type +class ENonStd { + int& xref; +}; + +// FStd is a standard layout type - all data members are standard layout types +class FStd { + AStd a; +}; + +// GNonStd is NOT a standard layout type - contains a non-standard-layout member +class GNonStd { + BNonStd b; +}; + +// HStd is a standard layout type - its base class is a standard layout type +struct HStd : AStd {}; + +// INonStd is NOT a standard layout type - its base class is not a standard +// layout type +struct INonStd : BNonStd {}; + +// JStd is a standard layout type +struct JStd { + static int x; +}; + +// KStd is a standard layout type - base class has no non-static data members +struct KStd : JStd {}; + +// LStd is a standard layout type - only one base class has non-static data +// members +struct LStd : AStd, JStd {}; + +// MNonStd is NOT a standard layout type - more than one base class with +// non-static data members +struct MNonStd : AStd, FStd {}; + +// Instantiations of NMaybeStd may or may not be standard layout types, +// depending on the template parameter. +template +struct NMaybeStd { + T x; +}; + +// Instantiation NMaybeStd is a standard layout type +NMaybeStd nmaybestd_astd; + +// Instantiation NMaybeStd is a standard layout type +NMaybeStd nmaybestd_int; + +// Instantiation NMaybeStd is NOT a standard layout type +NMaybeStd nmaybestd_bnonstd; + +// Instantiations of ONonStd cannot be standard layout types - regardless of the +// template parameter's type - since not all members have the same access +// control. +template +struct ONonStd { + T x; +private: + T y; +}; + +// Therefore instantiation ONonStd is NOT a standard layout type +ONonStd ononstd_int; diff --git a/cpp/ql/test/library-tests/std_layout/test.expected b/cpp/ql/test/library-tests/std_layout/test.expected new file mode 100644 index 000000000000..1070b1cfddab --- /dev/null +++ b/cpp/ql/test/library-tests/std_layout/test.expected @@ -0,0 +1,21 @@ +| file://:0:0:0:0 | __va_list_tag | standard layout | +| test.c:3:8:3:22 | PlainOldCStruct | standard layout | +| test.cpp:3:8:3:11 | AStd | standard layout | +| test.cpp:9:7:9:13 | BNonStd | NOT standard layout | +| test.cpp:16:7:16:13 | CNonStd | NOT standard layout | +| test.cpp:21:7:21:13 | DNonStd | NOT standard layout | +| test.cpp:25:7:25:13 | ENonStd | NOT standard layout | +| test.cpp:30:7:30:10 | FStd | standard layout | +| test.cpp:35:7:35:13 | GNonStd | NOT standard layout | +| test.cpp:40:8:40:11 | HStd | standard layout | +| test.cpp:44:8:44:14 | INonStd | NOT standard layout | +| test.cpp:47:8:47:11 | JStd | standard layout | +| test.cpp:52:8:52:11 | KStd | standard layout | +| test.cpp:56:8:56:11 | LStd | standard layout | +| test.cpp:60:8:60:14 | MNonStd | NOT standard layout | +| test.cpp:65:8:65:16 | NMaybeStd | standard layout | +| test.cpp:65:8:65:16 | NMaybeStd | NOT standard layout | +| test.cpp:65:8:65:16 | NMaybeStd | NOT standard layout | +| test.cpp:65:8:65:16 | NMaybeStd | standard layout | +| test.cpp:82:8:82:14 | ONonStd | NOT standard layout | +| test.cpp:82:8:82:14 | ONonStd | NOT standard layout | diff --git a/cpp/ql/test/library-tests/std_layout/test.ql b/cpp/ql/test/library-tests/std_layout/test.ql new file mode 100644 index 000000000000..f0f829e6110f --- /dev/null +++ b/cpp/ql/test/library-tests/std_layout/test.ql @@ -0,0 +1,5 @@ +import cpp + +from Class c, string s +where if c.isStandardLayout() then s = "standard layout" else s = "NOT standard layout" +select c, s diff --git a/cpp/ql/test/library-tests/structs/compatible_c/compatible.expected b/cpp/ql/test/library-tests/structs/compatible_c/compatible.expected index eb9e0d0529f7..23397167f046 100644 --- a/cpp/ql/test/library-tests/structs/compatible_c/compatible.expected +++ b/cpp/ql/test/library-tests/structs/compatible_c/compatible.expected @@ -54,7 +54,7 @@ | c1_gnu.c:7:8:7:12 | Lemon | 1 members | 1 locations | 0 | lemon_x | | c2_gnu.c:2:8:2:11 | Kiwi | 1 members | 1 locations | 0 | kiwi_x | | c2_gnu.c:7:8:7:12 | Lemon | 1 members | 1 locations | 0 | lemon_x | -| file://:0:0:0:0 | __va_list_tag | 4 members | 1 locations | 0 | gp_offset | -| file://:0:0:0:0 | __va_list_tag | 4 members | 1 locations | 1 | fp_offset | -| file://:0:0:0:0 | __va_list_tag | 4 members | 1 locations | 2 | overflow_arg_area | -| file://:0:0:0:0 | __va_list_tag | 4 members | 1 locations | 3 | reg_save_area | +| file://:0:0:0:0 | __va_list_tag | 4 members | 0 locations | 0 | gp_offset | +| file://:0:0:0:0 | __va_list_tag | 4 members | 0 locations | 1 | fp_offset | +| file://:0:0:0:0 | __va_list_tag | 4 members | 0 locations | 2 | overflow_arg_area | +| file://:0:0:0:0 | __va_list_tag | 4 members | 0 locations | 3 | reg_save_area | diff --git a/cpp/ql/test/library-tests/structs/compatible_cpp/compatible.expected b/cpp/ql/test/library-tests/structs/compatible_cpp/compatible.expected index 326fea4210f8..e33f5f851cd9 100644 --- a/cpp/ql/test/library-tests/structs/compatible_cpp/compatible.expected +++ b/cpp/ql/test/library-tests/structs/compatible_cpp/compatible.expected @@ -49,9 +49,9 @@ | b2.cpp:21:7:21:12 | Damson | 5 members | 2 locations | 1 | foo | | b2.cpp:21:7:21:12 | Damson | 5 members | 2 locations | 2 | operator= | | b2.cpp:21:7:21:12 | Damson | 5 members | 2 locations | 3 | operator= | -| file://:0:0:0:0 | __va_list_tag | 6 members | 1 locations | 0 | gp_offset | -| file://:0:0:0:0 | __va_list_tag | 6 members | 1 locations | 1 | fp_offset | -| file://:0:0:0:0 | __va_list_tag | 6 members | 1 locations | 2 | overflow_arg_area | -| file://:0:0:0:0 | __va_list_tag | 6 members | 1 locations | 3 | reg_save_area | -| file://:0:0:0:0 | __va_list_tag | 6 members | 1 locations | 4 | operator= | -| file://:0:0:0:0 | __va_list_tag | 6 members | 1 locations | 5 | operator= | +| file://:0:0:0:0 | __va_list_tag | 6 members | 0 locations | 0 | gp_offset | +| file://:0:0:0:0 | __va_list_tag | 6 members | 0 locations | 1 | fp_offset | +| file://:0:0:0:0 | __va_list_tag | 6 members | 0 locations | 2 | overflow_arg_area | +| file://:0:0:0:0 | __va_list_tag | 6 members | 0 locations | 3 | reg_save_area | +| file://:0:0:0:0 | __va_list_tag | 6 members | 0 locations | 4 | operator= | +| file://:0:0:0:0 | __va_list_tag | 6 members | 0 locations | 5 | operator= | diff --git a/cpp/ql/test/library-tests/structs/mutual_recursion/compatible_members.expected b/cpp/ql/test/library-tests/structs/mutual_recursion/compatible_members.expected index 92b8a908f755..da8965517905 100644 --- a/cpp/ql/test/library-tests/structs/mutual_recursion/compatible_members.expected +++ b/cpp/ql/test/library-tests/structs/mutual_recursion/compatible_members.expected @@ -24,7 +24,7 @@ | b.c:44:8:44:20 | IncompatibleD | 1 members | 2 locations | 0 | a | | b.c:51:8:51:20 | NonRecursiveA | 1 members | 1 locations | 0 | val | | b.c:55:8:55:20 | NonRecursiveB | 1 members | 1 locations | 0 | a | -| file://:0:0:0:0 | __va_list_tag | 4 members | 1 locations | 0 | gp_offset | -| file://:0:0:0:0 | __va_list_tag | 4 members | 1 locations | 1 | fp_offset | -| file://:0:0:0:0 | __va_list_tag | 4 members | 1 locations | 2 | overflow_arg_area | -| file://:0:0:0:0 | __va_list_tag | 4 members | 1 locations | 3 | reg_save_area | +| file://:0:0:0:0 | __va_list_tag | 4 members | 0 locations | 0 | gp_offset | +| file://:0:0:0:0 | __va_list_tag | 4 members | 0 locations | 1 | fp_offset | +| file://:0:0:0:0 | __va_list_tag | 4 members | 0 locations | 2 | overflow_arg_area | +| file://:0:0:0:0 | __va_list_tag | 4 members | 0 locations | 3 | reg_save_area | diff --git a/cpp/ql/test/library-tests/templates/CPP-203/decls.expected b/cpp/ql/test/library-tests/templates/CPP-203/decls.expected index c6f3122fe84f..1010a2891c67 100644 --- a/cpp/ql/test/library-tests/templates/CPP-203/decls.expected +++ b/cpp/ql/test/library-tests/templates/CPP-203/decls.expected @@ -7,6 +7,8 @@ | file://:0:0:0:0 | overflow_arg_area | | file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | reg_save_area | | test.cpp:2:16:2:16 | T | | test.cpp:3:8:3:8 | operator= | diff --git a/cpp/ql/test/library-tests/templates/CPP-204/element.expected b/cpp/ql/test/library-tests/templates/CPP-204/element.expected index 6c07e74e41c7..7d9516508ae9 100644 --- a/cpp/ql/test/library-tests/templates/CPP-204/element.expected +++ b/cpp/ql/test/library-tests/templates/CPP-204/element.expected @@ -3,10 +3,12 @@ | file://:0:0:0:0 | (global namespace) | | file://:0:0:0:0 | __va_list_tag | | file://:0:0:0:0 | __va_list_tag & | +| file://:0:0:0:0 | __va_list_tag && | | file://:0:0:0:0 | auto | | file://:0:0:0:0 | const EC | +| file://:0:0:0:0 | const __va_list_tag | +| file://:0:0:0:0 | const __va_list_tag & | | file://:0:0:0:0 | const bool | -| file://:0:0:0:0 | definition of __va_list_tag | | file://:0:0:0:0 | definition of fp_offset | | file://:0:0:0:0 | definition of gp_offset | | file://:0:0:0:0 | definition of overflow_arg_area | @@ -18,6 +20,8 @@ | file://:0:0:0:0 | operator= | | file://:0:0:0:0 | operator= | | file://:0:0:0:0 | overflow_arg_area | +| file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | reg_save_area | | file://:0:0:0:0 | void * | | test.cpp:0:0:0:0 | test.cpp | diff --git a/cpp/ql/test/library-tests/templates/CPP-223/decls.expected b/cpp/ql/test/library-tests/templates/CPP-223/decls.expected index c3ab0388d525..5709623a7910 100644 --- a/cpp/ql/test/library-tests/templates/CPP-223/decls.expected +++ b/cpp/ql/test/library-tests/templates/CPP-223/decls.expected @@ -6,6 +6,8 @@ | file://:0:0:0:0 | operator= | | file://:0:0:0:0 | operator= | | file://:0:0:0:0 | overflow_arg_area | +| file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | reg_save_area | | test.cpp:3:24:3:24 | b | | test.cpp:4:26:4:26 | c<> | @@ -13,6 +15,7 @@ | test.cpp:5:29:5:29 | e | | test.cpp:6:26:6:26 | p#0 | | test.cpp:6:29:6:31 | p#1 | +| test.cpp:7:20:7:20 | f | | test.cpp:7:20:7:26 | f | | test.cpp:7:28:7:28 | p#0 | | test.cpp:7:31:7:33 | p#1 | diff --git a/cpp/ql/test/library-tests/templates/decls/decls.expected b/cpp/ql/test/library-tests/templates/decls/decls.expected index 3789b785252b..43a691e53c9c 100644 --- a/cpp/ql/test/library-tests/templates/decls/decls.expected +++ b/cpp/ql/test/library-tests/templates/decls/decls.expected @@ -16,4 +16,6 @@ | file://:0:0:0:0 | operator= | | file://:0:0:0:0 | operator= | | file://:0:0:0:0 | overflow_arg_area | +| file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | reg_save_area | diff --git a/cpp/ql/test/library-tests/templates/destructors/destructors.expected b/cpp/ql/test/library-tests/templates/destructors/destructors.expected index d2a219e25b63..ad842c5e88e2 100644 --- a/cpp/ql/test/library-tests/templates/destructors/destructors.expected +++ b/cpp/ql/test/library-tests/templates/destructors/destructors.expected @@ -1,3 +1,3 @@ +| destructors.cpp:3:3:3:3 | ~Parameterized | destructors.cpp:2:8:2:20 | Parameterized | | destructors.cpp:3:3:3:16 | ~Parameterized | destructors.cpp:2:8:2:20 | Parameterized | -| destructors.cpp:3:3:3:16 | ~Parameterized | destructors.cpp:2:8:2:20 | Parameterized | | destructors.cpp:6:8:6:8 | ~Concrete | destructors.cpp:6:8:6:15 | Concrete | diff --git a/cpp/ql/test/library-tests/templates/extern/elements.expected b/cpp/ql/test/library-tests/templates/extern/elements.expected index 038f05ed3093..6e28f4cf28eb 100644 --- a/cpp/ql/test/library-tests/templates/extern/elements.expected +++ b/cpp/ql/test/library-tests/templates/extern/elements.expected @@ -3,8 +3,6 @@ | extern.cpp:1:20:1:20 | definition of T | | extern.cpp:2:5:2:5 | declaration of f | | extern.cpp:2:5:2:5 | f | -| extern.cpp:2:5:2:5 | f | | extern.cpp:2:7:2:7 | declaration of 1st parameter | | extern.cpp:2:7:2:7 | p#0 | -| extern.cpp:2:7:2:7 | p#0 | | extern.cpp:4:1:4:58 | // Currently we don't have an element for this declaration | diff --git a/cpp/ql/test/library-tests/templates/friends/decls.expected b/cpp/ql/test/library-tests/templates/friends/decls.expected index 7190a37e0574..2d73623d0d3c 100644 --- a/cpp/ql/test/library-tests/templates/friends/decls.expected +++ b/cpp/ql/test/library-tests/templates/friends/decls.expected @@ -1,7 +1,6 @@ | file://:0:0:0:0 | C's friend | | file://:0:0:0:0 | C's friend | | file://:0:0:0:0 | auto | -| file://:0:0:0:0 | f | | file://:0:0:0:0 | fp_offset | | file://:0:0:0:0 | gp_offset | | file://:0:0:0:0 | operator= | @@ -12,6 +11,7 @@ | file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | reg_save_area | | friends.cpp:2:19:2:21 | TTT | | friends.cpp:4:19:4:21 | TTT | @@ -28,6 +28,8 @@ | friends.cpp:7:9:7:9 | operator= | | friends.cpp:7:9:7:9 | operator= | | friends.cpp:7:9:7:9 | operator= | +| friends.cpp:9:17:9:17 | f | | friends.cpp:9:17:9:19 | C's friend | +| friends.cpp:9:21:9:26 | p#0 | | friends.cpp:12:17:12:17 | f | | friends.cpp:13:17:13:17 | f | diff --git a/cpp/ql/test/library-tests/templates/functions/functions.expected b/cpp/ql/test/library-tests/templates/functions/functions.expected index 15c847d45d25..b43bdb235358 100644 --- a/cpp/ql/test/library-tests/templates/functions/functions.expected +++ b/cpp/ql/test/library-tests/templates/functions/functions.expected @@ -1,8 +1,8 @@ | template_functions.cpp:3:8:3:8 | X | template_functions.cpp:6:5:6:15 | operator T * | template_functions.cpp:6:28:6:32 | (T *)... | | template_functions.cpp:3:8:3:8 | X | template_functions.cpp:6:5:6:15 | operator T * | template_functions.cpp:6:32:6:32 | 1 | -| template_functions.cpp:3:8:3:8 | X | template_functions.cpp:6:5:6:15 | operator int * | template_functions.cpp:6:28:6:32 | (int *)... | -| template_functions.cpp:3:8:3:8 | X | template_functions.cpp:6:5:6:15 | operator int * | template_functions.cpp:6:32:6:32 | 1 | +| template_functions.cpp:3:8:3:8 | X | template_functions.cpp:6:5:6:5 | operator int * | template_functions.cpp:6:28:6:32 | (int *)... | +| template_functions.cpp:3:8:3:8 | X | template_functions.cpp:6:5:6:5 | operator int * | template_functions.cpp:6:32:6:32 | 1 | | template_functions.cpp:13:10:13:14 | S | template_functions.cpp:16:5:16:15 | operator Q * | template_functions.cpp:16:28:16:32 | (Q *)... | | template_functions.cpp:13:10:13:14 | S | template_functions.cpp:16:5:16:15 | operator Q * | template_functions.cpp:16:32:16:32 | 2 | -| template_functions.cpp:13:10:13:14 | S | template_functions.cpp:16:5:16:15 | operator int * | template_functions.cpp:16:28:16:32 | (int *)... | -| template_functions.cpp:13:10:13:14 | S | template_functions.cpp:16:5:16:15 | operator int * | template_functions.cpp:16:32:16:32 | 2 | +| template_functions.cpp:13:10:13:14 | S | template_functions.cpp:16:5:16:5 | operator int * | template_functions.cpp:16:28:16:32 | (int *)... | +| template_functions.cpp:13:10:13:14 | S | template_functions.cpp:16:5:16:5 | operator int * | template_functions.cpp:16:32:16:32 | 2 | diff --git a/cpp/ql/test/library-tests/templates/incomplete_instantiations/test.expected b/cpp/ql/test/library-tests/templates/incomplete_instantiations/test.expected index 4743633a2c4e..1cbf49e14d63 100644 --- a/cpp/ql/test/library-tests/templates/incomplete_instantiations/test.expected +++ b/cpp/ql/test/library-tests/templates/incomplete_instantiations/test.expected @@ -1,15 +1,13 @@ | h.h:3:7:3:7 | C | h.h:4:10:4:12 | fun | 0 | | h.h:3:7:3:7 | C | h.h:3:7:3:7 | operator= | 0 | | h.h:3:7:3:7 | C | h.h:3:7:3:7 | operator= | 0 | -| h.h:3:7:3:7 | C | h.h:4:10:4:12 | fun | 0 | | h.h:3:7:3:7 | C | h.h:3:7:3:7 | operator= | 0 | | h.h:3:7:3:7 | C | h.h:3:7:3:7 | operator= | 0 | -| h.h:3:7:3:7 | C | h.h:4:10:4:12 | fun | 0 | | h.h:8:7:8:7 | D | h.h:10:10:10:12 | fun | 2 | | h.h:8:7:8:7 | D | h.h:8:7:8:7 | operator= | 0 | | h.h:8:7:8:7 | D | h.h:8:7:8:7 | operator= | 0 | -| h.h:8:7:8:7 | D | h.h:10:10:10:12 | fun | 2 | +| h.h:8:7:8:7 | D | h.h:10:10:10:10 | fun | 2 | | h.h:14:7:14:7 | E | h.h:16:10:16:12 | fun | 2 | | h.h:14:7:14:7 | E | h.h:14:7:14:7 | operator= | 0 | | h.h:14:7:14:7 | E | h.h:14:7:14:7 | operator= | 0 | -| h.h:14:7:14:7 | E | h.h:16:10:16:12 | fun | 2 | +| h.h:14:7:14:7 | E | h.h:16:10:16:10 | fun | 2 | diff --git a/cpp/ql/test/library-tests/templates/instantiation_directive/functions.expected b/cpp/ql/test/library-tests/templates/instantiation_directive/functions.expected index efee8321dbd5..672fae72e062 100644 --- a/cpp/ql/test/library-tests/templates/instantiation_directive/functions.expected +++ b/cpp/ql/test/library-tests/templates/instantiation_directive/functions.expected @@ -1,3 +1,3 @@ -| test.cpp:2:6:2:8 | foo | file://:0:0:0:0 | float | -| test.cpp:2:6:2:8 | foo | file://:0:0:0:0 | int | +| file://:0:0:0:0 | operator= | file://:0:0:0:0 | __va_list_tag && | +| file://:0:0:0:0 | operator= | file://:0:0:0:0 | const __va_list_tag & | | test.cpp:2:6:2:8 | foo | test.cpp:1:19:1:19 | T | diff --git a/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected b/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected index a368e9100f0a..994e6edb7a06 100644 --- a/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected +++ b/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected @@ -35,6 +35,7 @@ | file://:0:0:0:0 | __uptr | | file://:0:0:0:0 | __va_list_tag | | file://:0:0:0:0 | __va_list_tag & | +| file://:0:0:0:0 | __va_list_tag && | | file://:0:0:0:0 | abstract | | file://:0:0:0:0 | action * | | file://:0:0:0:0 | action *const | @@ -59,6 +60,8 @@ | file://:0:0:0:0 | composite && | | file://:0:0:0:0 | composite * | | file://:0:0:0:0 | const | +| file://:0:0:0:0 | const __va_list_tag | +| file://:0:0:0:0 | const __va_list_tag & | | file://:0:0:0:0 | const action>> | | file://:0:0:0:0 | const action>> & | | file://:0:0:0:0 | const actor1> | @@ -86,7 +89,6 @@ | file://:0:0:0:0 | declaration of 1st parameter | | file://:0:0:0:0 | declaration of 1st parameter | | file://:0:0:0:0 | decltype(nullptr) | -| file://:0:0:0:0 | definition of __va_list_tag | | file://:0:0:0:0 | definition of fp_offset | | file://:0:0:0:0 | definition of gp_offset | | file://:0:0:0:0 | definition of overflow_arg_area | @@ -141,6 +143,8 @@ | file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | +| file://:0:0:0:0 | p#0 | | file://:0:0:0:0 | private | | file://:0:0:0:0 | protected | | file://:0:0:0:0 | public | @@ -198,19 +202,20 @@ | header.h:4:5:4:10 | actor1 | | header.h:4:5:4:10 | actor1 | | header.h:4:5:4:10 | declaration of actor1 | -| header.h:4:5:4:10 | declaration of actor1 | | header.h:6:24:6:24 | A | | header.h:6:24:6:24 | definition of A | +| header.h:8:5:8:5 | definition of funx | +| header.h:8:5:8:5 | funx | | header.h:8:5:8:8 | declaration of funx | | header.h:8:5:8:8 | definition of funx | | header.h:8:5:8:8 | funx | | header.h:8:5:8:8 | funx | -| header.h:8:5:8:8 | funx | | header.h:8:13:8:14 | a_ | | header.h:8:13:8:14 | a_ | | header.h:8:13:8:14 | a_ | | header.h:8:13:8:14 | declaration of a_ | | header.h:8:13:8:14 | definition of a_ | +| header.h:8:13:8:14 | definition of a_ | | header.h:8:17:11:5 | { ... } | | header.h:8:17:11:5 | { ... } | | header.h:9:9:9:14 | declaration | @@ -251,22 +256,25 @@ | test.cpp:7:8:7:16 | definition of composite | | test.cpp:8:24:8:29 | TupleT | | test.cpp:8:24:8:29 | definition of TupleT | +| test.cpp:9:10:9:10 | definition of eval | +| test.cpp:9:10:9:10 | eval | | test.cpp:9:10:9:13 | declaration of eval | | test.cpp:9:10:9:13 | definition of eval | | test.cpp:9:10:9:13 | eval | | test.cpp:9:10:9:13 | eval | -| test.cpp:9:10:9:13 | eval | | test.cpp:9:22:9:25 | args | | test.cpp:9:22:9:25 | args | | test.cpp:9:22:9:25 | args | | test.cpp:9:22:9:25 | declaration of args | | test.cpp:9:22:9:25 | definition of args | +| test.cpp:9:22:9:25 | definition of args | | test.cpp:9:28:9:30 | { ... } | | test.cpp:9:28:9:30 | { ... } | | test.cpp:9:30:9:30 | return ... | | test.cpp:9:30:9:30 | return ... | | test.cpp:12:1:12:19 | #include "header.h" | | test.cpp:14:20:14:26 | ActionT | +| test.cpp:14:20:14:26 | ActionT | | test.cpp:14:20:14:26 | definition of ActionT | | test.cpp:15:7:15:7 | Unknown literal | | test.cpp:15:7:15:7 | action | @@ -289,9 +297,10 @@ | test.cpp:15:7:15:12 | action | | test.cpp:15:7:15:12 | action>> | | test.cpp:15:7:15:12 | definition of action | +| test.cpp:17:10:17:10 | definition of eparse | +| test.cpp:17:10:17:10 | eparse | | test.cpp:17:10:17:15 | definition of eparse | | test.cpp:17:10:17:15 | eparse | -| test.cpp:17:10:17:15 | eparse | | test.cpp:17:19:20:5 | { ... } | | test.cpp:17:19:20:5 | { ... } | | test.cpp:18:9:18:17 | declaration | @@ -327,10 +336,13 @@ | test.cpp:26:7:26:10 | definition of rule | | test.cpp:26:7:26:10 | rule | | test.cpp:28:28:28:34 | ParserT | +| test.cpp:28:28:28:34 | ParserT | | test.cpp:28:28:28:34 | definition of ParserT | +| test.cpp:29:9:29:9 | definition of rule | +| test.cpp:29:9:29:9 | rule | | test.cpp:29:9:29:12 | definition of rule | | test.cpp:29:9:29:12 | rule | -| test.cpp:29:9:29:12 | rule | +| test.cpp:29:22:29:22 | definition of p | | test.cpp:29:22:29:22 | definition of p | | test.cpp:29:22:29:22 | p | | test.cpp:29:22:29:22 | p | diff --git a/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/instantiations.expected b/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/instantiations.expected index 778353664010..e1c7f956c7b6 100644 --- a/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/instantiations.expected +++ b/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/instantiations.expected @@ -1,13 +1,12 @@ -| isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function | FunctionTemplateInstantiation | file://:0:0:0:0 | long | -| isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function | FunctionTemplateInstantiation | file://:0:0:0:0 | short | -| isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function | FunctionTemplateInstantiation | file://:0:0:0:0 | long | -| isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function | FunctionTemplateInstantiation | file://:0:0:0:0 | short | -| isfromtemplateinstantiation.cpp:38:26:38:42 | a_template_method | FunctionTemplateInstantiation | file://:0:0:0:0 | short | +| isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function | FunctionTemplateInstantiation | file://:0:0:0:0 | long | +| isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function | FunctionTemplateInstantiation | file://:0:0:0:0 | short | +| isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function | FunctionTemplateInstantiation | file://:0:0:0:0 | long | +| isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function | FunctionTemplateInstantiation | file://:0:0:0:0 | short | +| isfromtemplateinstantiation.cpp:38:26:38:26 | a_template_method | FunctionTemplateInstantiation | file://:0:0:0:0 | short | | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | ClassTemplateInstantiation | file://:0:0:0:0 | char | -| isfromtemplateinstantiation.cpp:53:26:53:42 | b_template_method | FunctionTemplateInstantiation | file://:0:0:0:0 | long | +| isfromtemplateinstantiation.cpp:53:26:53:26 | b_template_method | FunctionTemplateInstantiation | file://:0:0:0:0 | long | | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | ClassTemplateInstantiation | file://:0:0:0:0 | int | | isfromtemplateinstantiation.cpp:128:7:128:30 | AnotherTemplateClass | ClassTemplateInstantiation | file://:0:0:0:0 | long * | | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | ClassTemplateInstantiation | file://:0:0:0:0 | int | | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | ClassTemplateInstantiation | file://:0:0:0:0 | long | | load.cpp:13:7:13:27 | basic_text_iprimitive | ClassTemplateInstantiation | load.cpp:3:7:3:24 | std_istream_mockup | -| load.cpp:22:10:22:13 | load | FunctionTemplateInstantiation | file://:0:0:0:0 | short | diff --git a/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/isfromtemplateinstantiation.expected b/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/isfromtemplateinstantiation.expected index dc73e749a4cd..a97246dd8991 100644 --- a/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/isfromtemplateinstantiation.expected +++ b/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/isfromtemplateinstantiation.expected @@ -1,67 +1,78 @@ -| isfromtemplateinstantiation.cpp:13:1:17:1 | { ... } | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:13:1:17:1 | { ... } | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:14:2:14:5 | declaration | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:14:2:14:5 | declaration | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:14:4:14:4 | definition of t | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:14:4:14:4 | definition of t | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:14:4:14:4 | t | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:14:4:14:4 | t | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:16:2:16:2 | t | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:16:2:16:2 | t | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:16:2:16:4 | ... ++ | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:16:2:16:4 | ... ++ | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:16:2:16:5 | ExprStmt | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:16:2:16:5 | ExprStmt | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:17:1:17:1 | return ... | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:17:1:17:1 | return ... | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function() | -| isfromtemplateinstantiation.cpp:20:1:26:1 | { ... } | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:20:1:26:1 | { ... } | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:21:2:21:11 | declaration | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:21:2:21:11 | declaration | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:21:6:21:6 | definition of i | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:21:6:21:6 | definition of i | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:21:6:21:6 | i | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:21:6:21:6 | i | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:21:9:21:10 | 0 | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:21:9:21:10 | 0 | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:21:9:21:10 | initializer for i | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:21:9:21:10 | initializer for i | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:23:2:23:8 | ... ++ | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:23:2:23:8 | ... ++ | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:23:2:23:8 | i | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:23:2:23:8 | i | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:23:2:23:9 | ExprStmt | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:23:2:23:9 | ExprStmt | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:25:2:25:27 | call to inner_template_function | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:25:2:25:27 | call to inner_template_function | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:25:2:25:30 | ExprStmt | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:25:2:25:30 | ExprStmt | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:26:1:26:1 | return ... | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:26:1:26:1 | return ... | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function() | -| isfromtemplateinstantiation.cpp:39:2:40:2 | { ... } | isfromtemplateinstantiation.cpp:38:26:38:42 | normal_class::a_template_method() | -| isfromtemplateinstantiation.cpp:40:2:40:2 | return ... | isfromtemplateinstantiation.cpp:38:26:38:42 | normal_class::a_template_method() | +| isfromtemplateinstantiation.cpp:12:24:12:24 | definition of inner_template_function | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:12:24:12:24 | definition of inner_template_function | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:13:1:17:1 | { ... } | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:13:1:17:1 | { ... } | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:14:2:14:5 | declaration | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:14:2:14:5 | declaration | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:14:4:14:4 | definition of t | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:14:4:14:4 | definition of t | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:14:4:14:4 | t | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:14:4:14:4 | t | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:16:2:16:2 | t | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:16:2:16:2 | t | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:16:2:16:4 | ... ++ | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:16:2:16:4 | ... ++ | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:16:2:16:5 | ExprStmt | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:16:2:16:5 | ExprStmt | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:17:1:17:1 | return ... | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:17:1:17:1 | return ... | isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function() | +| isfromtemplateinstantiation.cpp:19:24:19:24 | definition of outer_template_function | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:19:24:19:24 | definition of outer_template_function | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:20:1:26:1 | { ... } | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:20:1:26:1 | { ... } | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:21:2:21:11 | declaration | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:21:2:21:11 | declaration | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:21:6:21:6 | definition of i | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:21:6:21:6 | definition of i | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:21:6:21:6 | i | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:21:6:21:6 | i | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:21:9:21:10 | 0 | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:21:9:21:10 | 0 | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:21:9:21:10 | initializer for i | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:21:9:21:10 | initializer for i | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:23:2:23:8 | ... ++ | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:23:2:23:8 | ... ++ | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:23:2:23:8 | i | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:23:2:23:8 | i | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:23:2:23:9 | ExprStmt | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:23:2:23:9 | ExprStmt | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:25:2:25:27 | call to inner_template_function | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:25:2:25:27 | call to inner_template_function | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:25:2:25:30 | ExprStmt | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:25:2:25:30 | ExprStmt | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:26:1:26:1 | return ... | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:26:1:26:1 | return ... | isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function() | +| isfromtemplateinstantiation.cpp:38:26:38:26 | definition of a_template_method | isfromtemplateinstantiation.cpp:38:26:38:26 | normal_class::a_template_method() | +| isfromtemplateinstantiation.cpp:39:2:40:2 | { ... } | isfromtemplateinstantiation.cpp:38:26:38:26 | normal_class::a_template_method() | +| isfromtemplateinstantiation.cpp:40:2:40:2 | return ... | isfromtemplateinstantiation.cpp:38:26:38:26 | normal_class::a_template_method() | | isfromtemplateinstantiation.cpp:44:26:44:26 | declaration of operator= | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | | isfromtemplateinstantiation.cpp:44:26:44:26 | declaration of operator= | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | | isfromtemplateinstantiation.cpp:44:26:44:26 | template_class::operator=(const template_class &) | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | | isfromtemplateinstantiation.cpp:44:26:44:26 | template_class::operator=(template_class &&) | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | | isfromtemplateinstantiation.cpp:46:4:46:4 | definition of t | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | | isfromtemplateinstantiation.cpp:46:4:46:4 | t | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | -| isfromtemplateinstantiation.cpp:49:7:49:14 | template_class::b_method() | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | +| isfromtemplateinstantiation.cpp:49:7:49:7 | definition of b_method | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | +| isfromtemplateinstantiation.cpp:49:7:49:7 | definition of b_method | isfromtemplateinstantiation.cpp:49:7:49:7 | template_class::b_method() | +| isfromtemplateinstantiation.cpp:49:7:49:7 | template_class::b_method() | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | | isfromtemplateinstantiation.cpp:50:2:51:2 | { ... } | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | -| isfromtemplateinstantiation.cpp:50:2:51:2 | { ... } | isfromtemplateinstantiation.cpp:49:7:49:14 | template_class::b_method() | +| isfromtemplateinstantiation.cpp:50:2:51:2 | { ... } | isfromtemplateinstantiation.cpp:49:7:49:7 | template_class::b_method() | | isfromtemplateinstantiation.cpp:51:2:51:2 | return ... | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | -| isfromtemplateinstantiation.cpp:51:2:51:2 | return ... | isfromtemplateinstantiation.cpp:49:7:49:14 | template_class::b_method() | +| isfromtemplateinstantiation.cpp:51:2:51:2 | return ... | isfromtemplateinstantiation.cpp:49:7:49:7 | template_class::b_method() | +| isfromtemplateinstantiation.cpp:53:26:53:26 | definition of b_template_method | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | +| isfromtemplateinstantiation.cpp:53:26:53:26 | definition of b_template_method | isfromtemplateinstantiation.cpp:53:26:53:26 | template_class::b_template_method(long) | +| isfromtemplateinstantiation.cpp:53:26:53:26 | template_class::b_template_method(long) | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | | isfromtemplateinstantiation.cpp:53:26:53:42 | declaration of b_template_method | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | | isfromtemplateinstantiation.cpp:53:26:53:42 | template_class::b_template_method(U) | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | -| isfromtemplateinstantiation.cpp:53:26:53:42 | template_class::b_template_method(long) | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | | isfromtemplateinstantiation.cpp:53:46:53:46 | U u | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | | isfromtemplateinstantiation.cpp:53:46:53:46 | declaration of u | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | +| isfromtemplateinstantiation.cpp:53:46:53:46 | definition of u | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | +| isfromtemplateinstantiation.cpp:53:46:53:46 | definition of u | isfromtemplateinstantiation.cpp:53:26:53:26 | template_class::b_template_method(long) | | isfromtemplateinstantiation.cpp:53:46:53:46 | long u | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | -| isfromtemplateinstantiation.cpp:53:46:53:46 | long u | isfromtemplateinstantiation.cpp:53:26:53:42 | template_class::b_template_method(long) | +| isfromtemplateinstantiation.cpp:53:46:53:46 | long u | isfromtemplateinstantiation.cpp:53:26:53:26 | template_class::b_template_method(long) | | isfromtemplateinstantiation.cpp:54:2:55:2 | { ... } | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | -| isfromtemplateinstantiation.cpp:54:2:55:2 | { ... } | isfromtemplateinstantiation.cpp:53:26:53:42 | template_class::b_template_method(long) | +| isfromtemplateinstantiation.cpp:54:2:55:2 | { ... } | isfromtemplateinstantiation.cpp:53:26:53:26 | template_class::b_template_method(long) | | isfromtemplateinstantiation.cpp:55:2:55:2 | return ... | isfromtemplateinstantiation.cpp:44:26:44:39 | template_class | -| isfromtemplateinstantiation.cpp:55:2:55:2 | return ... | isfromtemplateinstantiation.cpp:53:26:53:42 | template_class::b_template_method(long) | +| isfromtemplateinstantiation.cpp:55:2:55:2 | return ... | isfromtemplateinstantiation.cpp:53:26:53:26 | template_class::b_template_method(long) | | isfromtemplateinstantiation.cpp:77:26:77:26 | AnotherTemplateClass::operator=(AnotherTemplateClass &&) | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | isfromtemplateinstantiation.cpp:77:26:77:26 | AnotherTemplateClass::operator=(const AnotherTemplateClass &) | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | isfromtemplateinstantiation.cpp:77:26:77:26 | AnotherTemplateClass::operator=(AnotherTemplateClass &&) | isfromtemplateinstantiation.cpp:128:7:128:30 | AnotherTemplateClass | @@ -86,32 +97,33 @@ | isfromtemplateinstantiation.cpp:86:16:86:16 | definition of l | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | isfromtemplateinstantiation.cpp:86:16:86:16 | l | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | isfromtemplateinstantiation.cpp:90:3:90:18 | MyClassEnumConst | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:93:7:93:15 | AnotherTemplateClass::myMethod1(MyClassEnum) | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:93:7:93:7 | AnotherTemplateClass::myMethod1(MyClassEnum) | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:93:7:93:7 | definition of myMethod1 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:93:7:93:7 | definition of myMethod1 | isfromtemplateinstantiation.cpp:93:7:93:7 | AnotherTemplateClass::myMethod1(MyClassEnum) | | isfromtemplateinstantiation.cpp:93:29:93:32 | MyClassEnum mce1 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:93:29:93:32 | MyClassEnum mce1 | isfromtemplateinstantiation.cpp:93:7:93:15 | AnotherTemplateClass::myMethod1(MyClassEnum) | +| isfromtemplateinstantiation.cpp:93:29:93:32 | MyClassEnum mce1 | isfromtemplateinstantiation.cpp:93:7:93:7 | AnotherTemplateClass::myMethod1(MyClassEnum) | +| isfromtemplateinstantiation.cpp:93:29:93:32 | definition of mce1 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:93:29:93:32 | definition of mce1 | isfromtemplateinstantiation.cpp:93:7:93:7 | AnotherTemplateClass::myMethod1(MyClassEnum) | | isfromtemplateinstantiation.cpp:93:36:93:51 | MyClassEnumConst | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:93:36:93:51 | MyClassEnumConst | isfromtemplateinstantiation.cpp:93:7:93:15 | AnotherTemplateClass::myMethod1(MyClassEnum) | +| isfromtemplateinstantiation.cpp:93:36:93:51 | MyClassEnumConst | isfromtemplateinstantiation.cpp:93:7:93:7 | AnotherTemplateClass::myMethod1(MyClassEnum) | | isfromtemplateinstantiation.cpp:93:54:93:55 | { ... } | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:93:54:93:55 | { ... } | isfromtemplateinstantiation.cpp:93:7:93:15 | AnotherTemplateClass::myMethod1(MyClassEnum) | +| isfromtemplateinstantiation.cpp:93:54:93:55 | { ... } | isfromtemplateinstantiation.cpp:93:7:93:7 | AnotherTemplateClass::myMethod1(MyClassEnum) | | isfromtemplateinstantiation.cpp:93:55:93:55 | return ... | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:93:55:93:55 | return ... | isfromtemplateinstantiation.cpp:93:7:93:15 | AnotherTemplateClass::myMethod1(MyClassEnum) | +| isfromtemplateinstantiation.cpp:93:55:93:55 | return ... | isfromtemplateinstantiation.cpp:93:7:93:7 | AnotherTemplateClass::myMethod1(MyClassEnum) | +| isfromtemplateinstantiation.cpp:94:29:94:32 | definition of mce2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:94:29:94:32 | definition of mce2 | isfromtemplateinstantiation.cpp:97:52:97:52 | AnotherTemplateClass::myMethod2(MyClassEnum) | | isfromtemplateinstantiation.cpp:94:36:94:51 | MyClassEnumConst | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:94:36:94:51 | MyClassEnumConst | isfromtemplateinstantiation.cpp:97:25:97:60 | AnotherTemplateClass::myMethod2(MyClassEnum) | -| isfromtemplateinstantiation.cpp:97:25:97:60 | AnotherTemplateClass::myMethod2(MyClassEnum) | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:94:36:94:51 | MyClassEnumConst | isfromtemplateinstantiation.cpp:97:52:97:52 | AnotherTemplateClass::myMethod2(MyClassEnum) | +| isfromtemplateinstantiation.cpp:97:52:97:52 | AnotherTemplateClass::myMethod2(MyClassEnum) | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:97:52:97:52 | definition of myMethod2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:97:52:97:52 | definition of myMethod2 | isfromtemplateinstantiation.cpp:97:52:97:52 | AnotherTemplateClass::myMethod2(MyClassEnum) | | isfromtemplateinstantiation.cpp:97:74:97:77 | MyClassEnum mce2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:97:74:97:77 | MyClassEnum mce2 | isfromtemplateinstantiation.cpp:97:25:97:60 | AnotherTemplateClass::myMethod2(MyClassEnum) | +| isfromtemplateinstantiation.cpp:97:74:97:77 | MyClassEnum mce2 | isfromtemplateinstantiation.cpp:97:52:97:52 | AnotherTemplateClass::myMethod2(MyClassEnum) | | isfromtemplateinstantiation.cpp:98:1:99:1 | { ... } | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:98:1:99:1 | { ... } | isfromtemplateinstantiation.cpp:97:25:97:60 | AnotherTemplateClass::myMethod2(MyClassEnum) | +| isfromtemplateinstantiation.cpp:98:1:99:1 | { ... } | isfromtemplateinstantiation.cpp:97:52:97:52 | AnotherTemplateClass::myMethod2(MyClassEnum) | | isfromtemplateinstantiation.cpp:99:1:99:1 | return ... | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:99:1:99:1 | return ... | isfromtemplateinstantiation.cpp:97:25:97:60 | AnotherTemplateClass::myMethod2(MyClassEnum) | +| isfromtemplateinstantiation.cpp:99:1:99:1 | return ... | isfromtemplateinstantiation.cpp:97:52:97:52 | AnotherTemplateClass::myMethod2(MyClassEnum) | | isfromtemplateinstantiation.cpp:110:3:110:3 | definition of var_template | isfromtemplateinstantiation.cpp:110:3:110:3 | var_template | -| isfromtemplateinstantiation.cpp:129:6:129:6 | AnotherTemplateClass::f() | isfromtemplateinstantiation.cpp:128:7:128:30 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:129:10:129:22 | { ... } | isfromtemplateinstantiation.cpp:128:7:128:30 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:129:10:129:22 | { ... } | isfromtemplateinstantiation.cpp:129:6:129:6 | AnotherTemplateClass::f() | -| isfromtemplateinstantiation.cpp:129:12:129:20 | return ... | isfromtemplateinstantiation.cpp:128:7:128:30 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:129:12:129:20 | return ... | isfromtemplateinstantiation.cpp:129:6:129:6 | AnotherTemplateClass::f() | -| isfromtemplateinstantiation.cpp:129:19:129:19 | 1 | isfromtemplateinstantiation.cpp:128:7:128:30 | AnotherTemplateClass | -| isfromtemplateinstantiation.cpp:129:19:129:19 | 1 | isfromtemplateinstantiation.cpp:129:6:129:6 | AnotherTemplateClass::f() | | isfromtemplateinstantiation.cpp:134:29:134:29 | Outer::operator=(Outer &&) | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | | isfromtemplateinstantiation.cpp:134:29:134:29 | Outer::operator=(const Outer &) | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | | isfromtemplateinstantiation.cpp:134:29:134:29 | declaration of operator= | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | @@ -120,6 +132,8 @@ | isfromtemplateinstantiation.cpp:135:31:135:31 | Outer::Inner::operator=(const Inner &) | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | | isfromtemplateinstantiation.cpp:135:31:135:31 | declaration of operator= | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | | isfromtemplateinstantiation.cpp:135:31:135:31 | declaration of operator= | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | +| isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | +| isfromtemplateinstantiation.cpp:135:31:135:35 | declaration of Inner | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | | isfromtemplateinstantiation.cpp:136:7:136:7 | definition of x | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | | isfromtemplateinstantiation.cpp:136:7:136:7 | x | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | | isfromtemplateinstantiation.cpp:137:7:137:7 | definition of y | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | @@ -132,78 +146,7 @@ | load.cpp:13:7:13:7 | definition of operator= | load.cpp:13:7:13:27 | basic_text_iprimitive | | load.cpp:15:14:15:15 | definition of is | load.cpp:13:7:13:27 | basic_text_iprimitive | | load.cpp:15:14:15:15 | is | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:18:5:18:25 | basic_text_iprimitive::basic_text_iprimitive(std_istream_mockup &) | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:18:36:18:42 | std_istream_mockup & isParam | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:18:36:18:42 | std_istream_mockup & isParam | load.cpp:18:5:18:25 | basic_text_iprimitive::basic_text_iprimitive(std_istream_mockup &) | -| load.cpp:19:11:19:21 | constructor init of field is | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:19:11:19:21 | constructor init of field is | load.cpp:18:5:18:25 | basic_text_iprimitive::basic_text_iprimitive(std_istream_mockup &) | -| load.cpp:19:14:19:20 | (reference dereference) | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:19:14:19:20 | (reference dereference) | load.cpp:18:5:18:25 | basic_text_iprimitive::basic_text_iprimitive(std_istream_mockup &) | -| load.cpp:19:14:19:20 | (reference to) | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:19:14:19:20 | (reference to) | load.cpp:18:5:18:25 | basic_text_iprimitive::basic_text_iprimitive(std_istream_mockup &) | -| load.cpp:19:14:19:20 | isParam | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:19:14:19:20 | isParam | load.cpp:18:5:18:25 | basic_text_iprimitive::basic_text_iprimitive(std_istream_mockup &) | -| load.cpp:19:23:19:24 | { ... } | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:19:23:19:24 | { ... } | load.cpp:18:5:18:25 | basic_text_iprimitive::basic_text_iprimitive(std_istream_mockup &) | -| load.cpp:19:24:19:24 | return ... | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:19:24:19:24 | return ... | load.cpp:18:5:18:25 | basic_text_iprimitive::basic_text_iprimitive(std_istream_mockup &) | | load.cpp:22:10:22:13 | basic_text_iprimitive::load(T &) | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | load.cpp:13:7:13:27 | basic_text_iprimitive | | load.cpp:22:10:22:13 | declaration of load | load.cpp:13:7:13:27 | basic_text_iprimitive | | load.cpp:22:19:22:19 | T & t | load.cpp:13:7:13:27 | basic_text_iprimitive | | load.cpp:22:19:22:19 | declaration of t | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:22:19:22:19 | short & t | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:22:19:22:19 | short & t | load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | -| load.cpp:23:5:25:5 | { ... } | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:23:5:25:5 | { ... } | load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | -| load.cpp:24:9:24:10 | (reference dereference) | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:24:9:24:10 | (reference dereference) | load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | -| load.cpp:24:9:24:10 | is | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:24:9:24:10 | is | load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | -| load.cpp:24:9:24:16 | ExprStmt | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:24:9:24:16 | ExprStmt | load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | -| load.cpp:24:12:24:12 | call to operator>> | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:24:12:24:12 | call to operator>> | load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | -| load.cpp:24:12:24:16 | (reference dereference) | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:24:12:24:16 | (reference dereference) | load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | -| load.cpp:24:15:24:15 | (reference dereference) | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:24:15:24:15 | (reference dereference) | load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | -| load.cpp:24:15:24:15 | (reference to) | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:24:15:24:15 | (reference to) | load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | -| load.cpp:24:15:24:15 | t | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:24:15:24:15 | t | load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | -| load.cpp:25:5:25:5 | return ... | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:25:5:25:5 | return ... | load.cpp:22:10:22:13 | basic_text_iprimitive::load(short &) | -| load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:27:22:27:22 | char & t | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:27:22:27:22 | char & t | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:28:5:32:5 | { ... } | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:28:5:32:5 | { ... } | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:29:9:29:20 | declaration | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:29:9:29:20 | declaration | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:29:19:29:19 | definition of i | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:29:19:29:19 | definition of i | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:29:19:29:19 | i | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:29:19:29:19 | i | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:30:9:30:12 | call to load | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:30:9:30:12 | call to load | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:30:9:30:16 | ExprStmt | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:30:9:30:16 | ExprStmt | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:30:14:30:14 | (reference to) | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:30:14:30:14 | (reference to) | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:30:14:30:14 | i | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:30:14:30:14 | i | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:31:9:31:9 | (reference dereference) | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:31:9:31:9 | (reference dereference) | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:31:9:31:9 | t | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:31:9:31:9 | t | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:31:9:31:13 | ... = ... | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:31:9:31:13 | ... = ... | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:31:9:31:14 | ExprStmt | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:31:9:31:14 | ExprStmt | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:31:13:31:13 | (char)... | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:31:13:31:13 | (char)... | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:31:13:31:13 | i | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:31:13:31:13 | i | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | -| load.cpp:32:5:32:5 | return ... | load.cpp:13:7:13:27 | basic_text_iprimitive | -| load.cpp:32:5:32:5 | return ... | load.cpp:27:10:27:13 | basic_text_iprimitive::load(char &) | diff --git a/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/isfromuninstantiatedtemplate.expected b/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/isfromuninstantiatedtemplate.expected index 74a096dff1aa..b5b562f95eb5 100644 --- a/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/isfromuninstantiatedtemplate.expected +++ b/cpp/ql/test/library-tests/templates/isfromtemplateinstantiation/isfromuninstantiatedtemplate.expected @@ -1,13 +1,9 @@ isFromUninstantiatedTemplate | file://:0:0:0:0 | 0 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| file://:0:0:0:0 | 0 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| file://:0:0:0:0 | (int)... | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | file://:0:0:0:0 | (int)... | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| file://:0:0:0:0 | Inner | file://:0:0:0:0 | Inner | | file://:0:0:0:0 | declaration of 1st parameter | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | | file://:0:0:0:0 | declaration of 1st parameter | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | | file://:0:0:0:0 | initializer for MyClassEnumConst | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | -| file://:0:0:0:0 | initializer for MyClassEnumConst | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | file://:0:0:0:0 | p#0 | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | | file://:0:0:0:0 | p#0 | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | | file://:0:0:0:0 | this | load.cpp:13:7:13:27 | basic_text_iprimitive | @@ -100,20 +96,37 @@ isFromUninstantiatedTemplate | isfromtemplateinstantiation.cpp:93:55:93:55 | return ... | isfromtemplateinstantiation.cpp:93:7:93:15 | myMethod1 | | isfromtemplateinstantiation.cpp:94:7:94:15 | declaration of myMethod2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | isfromtemplateinstantiation.cpp:94:7:94:15 | declaration of myMethod2 | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | +| isfromtemplateinstantiation.cpp:94:7:94:15 | declaration of myMethod2 | isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | | isfromtemplateinstantiation.cpp:94:29:94:32 | declaration of mce2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | isfromtemplateinstantiation.cpp:94:29:94:32 | declaration of mce2 | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | +| isfromtemplateinstantiation.cpp:94:29:94:32 | declaration of mce2 | isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | | isfromtemplateinstantiation.cpp:97:25:97:60 | definition of myMethod2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | isfromtemplateinstantiation.cpp:97:25:97:60 | definition of myMethod2 | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | +| isfromtemplateinstantiation.cpp:97:25:97:60 | definition of myMethod2 | isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | +| isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | +| isfromtemplateinstantiation.cpp:97:52:97:52 | definition of myMethod2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:97:52:97:52 | definition of myMethod2 | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | +| isfromtemplateinstantiation.cpp:97:52:97:52 | definition of myMethod2 | isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | +| isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | +| isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | | isfromtemplateinstantiation.cpp:97:74:97:77 | definition of mce2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:97:74:97:77 | definition of mce2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | +| isfromtemplateinstantiation.cpp:97:74:97:77 | definition of mce2 | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | | isfromtemplateinstantiation.cpp:97:74:97:77 | definition of mce2 | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | +| isfromtemplateinstantiation.cpp:97:74:97:77 | definition of mce2 | isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | +| isfromtemplateinstantiation.cpp:97:74:97:77 | definition of mce2 | isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | | isfromtemplateinstantiation.cpp:97:74:97:77 | mce2 | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | isfromtemplateinstantiation.cpp:97:74:97:77 | mce2 | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | +| isfromtemplateinstantiation.cpp:97:74:97:77 | mce2 | isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | | isfromtemplateinstantiation.cpp:98:1:99:1 | { ... } | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | isfromtemplateinstantiation.cpp:98:1:99:1 | { ... } | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | +| isfromtemplateinstantiation.cpp:98:1:99:1 | { ... } | isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | | isfromtemplateinstantiation.cpp:99:1:99:1 | return ... | isfromtemplateinstantiation.cpp:77:26:77:45 | AnotherTemplateClass | | isfromtemplateinstantiation.cpp:99:1:99:1 | return ... | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | +| isfromtemplateinstantiation.cpp:99:1:99:1 | return ... | isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | | isfromtemplateinstantiation.cpp:110:15:110:15 | definition of var_template | isfromtemplateinstantiation.cpp:110:15:110:15 | var_template | | isfromtemplateinstantiation.cpp:110:15:110:15 | var_template | isfromtemplateinstantiation.cpp:110:15:110:15 | var_template | | isfromtemplateinstantiation.cpp:128:7:128:30 | AnotherTemplateClass | isfromtemplateinstantiation.cpp:128:7:128:30 | AnotherTemplateClass | @@ -136,7 +149,9 @@ isFromUninstantiatedTemplate | isfromtemplateinstantiation.cpp:135:31:135:31 | operator= | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | +| isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | +| isfromtemplateinstantiation.cpp:135:31:135:35 | declaration of Inner | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | | isfromtemplateinstantiation.cpp:135:31:135:35 | definition of Inner | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | | isfromtemplateinstantiation.cpp:135:31:135:35 | definition of Inner | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | | isfromtemplateinstantiation.cpp:136:7:136:7 | definition of x | isfromtemplateinstantiation.cpp:134:29:134:33 | Outer | @@ -257,9 +272,11 @@ isFromUninstantiatedTemplate | load.cpp:32:5:32:5 | return ... | load.cpp:27:10:27:13 | load | #select | isfromtemplateinstantiation.cpp:4:6:4:20 | normal_function | | | Declaration | | +| isfromtemplateinstantiation.cpp:12:24:12:24 | definition of inner_template_function | I | | Definition | | +| isfromtemplateinstantiation.cpp:12:24:12:24 | definition of inner_template_function | I | | Definition | | +| isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function | I | | Declaration | | +| isfromtemplateinstantiation.cpp:12:24:12:24 | inner_template_function | I | | Declaration | | | isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function | | T | Declaration | | -| isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function | I | | Declaration | | -| isfromtemplateinstantiation.cpp:12:24:12:46 | inner_template_function | I | | Declaration | | | isfromtemplateinstantiation.cpp:13:1:17:1 | { ... } | | T | Stmt | | | isfromtemplateinstantiation.cpp:13:1:17:1 | { ... } | I | | Stmt | | | isfromtemplateinstantiation.cpp:13:1:17:1 | { ... } | I | | Stmt | | @@ -284,9 +301,11 @@ isFromUninstantiatedTemplate | isfromtemplateinstantiation.cpp:17:1:17:1 | return ... | | T | Stmt | | | isfromtemplateinstantiation.cpp:17:1:17:1 | return ... | I | | Stmt | | | isfromtemplateinstantiation.cpp:17:1:17:1 | return ... | I | | Stmt | | +| isfromtemplateinstantiation.cpp:19:24:19:24 | definition of outer_template_function | I | | Definition | | +| isfromtemplateinstantiation.cpp:19:24:19:24 | definition of outer_template_function | I | | Definition | | +| isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function | I | | Declaration | | +| isfromtemplateinstantiation.cpp:19:24:19:24 | outer_template_function | I | | Declaration | | | isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function | | T | Declaration | | -| isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function | I | | Declaration | | -| isfromtemplateinstantiation.cpp:19:24:19:46 | outer_template_function | I | | Declaration | | | isfromtemplateinstantiation.cpp:20:1:26:1 | { ... } | | T | Stmt | | | isfromtemplateinstantiation.cpp:20:1:26:1 | { ... } | I | | Stmt | | | isfromtemplateinstantiation.cpp:20:1:26:1 | { ... } | I | | Stmt | | @@ -308,6 +327,9 @@ isFromUninstantiatedTemplate | isfromtemplateinstantiation.cpp:23:2:23:8 | ... ++ | | T | Expr | | | isfromtemplateinstantiation.cpp:23:2:23:8 | ... ++ | I | | Expr | | | isfromtemplateinstantiation.cpp:23:2:23:8 | ... ++ | I | | Expr | | +| isfromtemplateinstantiation.cpp:23:2:23:8 | A_MACRO | | | other | | +| isfromtemplateinstantiation.cpp:23:2:23:8 | A_MACRO | | | other | | +| isfromtemplateinstantiation.cpp:23:2:23:8 | A_MACRO | | | other | | | isfromtemplateinstantiation.cpp:23:2:23:8 | i | | T | Expr | Not ref | | isfromtemplateinstantiation.cpp:23:2:23:8 | i | I | | Expr | Not ref | | isfromtemplateinstantiation.cpp:23:2:23:8 | i | I | | Expr | Not ref | @@ -329,8 +351,8 @@ isFromUninstantiatedTemplate | isfromtemplateinstantiation.cpp:29:7:29:7 | operator= | | | Declaration | | | isfromtemplateinstantiation.cpp:29:7:29:18 | normal_class | | | Declaration | | | isfromtemplateinstantiation.cpp:34:7:34:14 | a_method | | | Declaration | | +| isfromtemplateinstantiation.cpp:38:26:38:26 | a_template_method | I | | Declaration | | | isfromtemplateinstantiation.cpp:38:26:38:42 | a_template_method | | T | Declaration | | -| isfromtemplateinstantiation.cpp:38:26:38:42 | a_template_method | I | | Declaration | | | isfromtemplateinstantiation.cpp:39:2:40:2 | { ... } | | T | Stmt | | | isfromtemplateinstantiation.cpp:39:2:40:2 | { ... } | I | | Stmt | | | isfromtemplateinstantiation.cpp:40:2:40:2 | return ... | | T | Stmt | | @@ -345,15 +367,17 @@ isFromUninstantiatedTemplate | isfromtemplateinstantiation.cpp:46:4:46:4 | definition of t | I | | Definition | | | isfromtemplateinstantiation.cpp:46:4:46:4 | t | | T | Declaration | | | isfromtemplateinstantiation.cpp:46:4:46:4 | t | I | | Declaration | | +| isfromtemplateinstantiation.cpp:49:7:49:7 | b_method | I | | Declaration | | | isfromtemplateinstantiation.cpp:49:7:49:14 | b_method | | T | Declaration | | -| isfromtemplateinstantiation.cpp:49:7:49:14 | b_method | I | | Declaration | | | isfromtemplateinstantiation.cpp:50:2:51:2 | { ... } | | T | Stmt | | | isfromtemplateinstantiation.cpp:50:2:51:2 | { ... } | I | | Stmt | | | isfromtemplateinstantiation.cpp:51:2:51:2 | return ... | | T | Stmt | | | isfromtemplateinstantiation.cpp:51:2:51:2 | return ... | I | | Stmt | | +| isfromtemplateinstantiation.cpp:53:26:53:26 | b_template_method | I | | Declaration | | | isfromtemplateinstantiation.cpp:53:26:53:42 | b_template_method | | T | Declaration | | -| isfromtemplateinstantiation.cpp:53:26:53:42 | b_template_method | I | | Declaration | | | isfromtemplateinstantiation.cpp:53:26:53:42 | b_template_method | I | T | Declaration | | +| isfromtemplateinstantiation.cpp:53:46:53:46 | definition of u | | T | Definition | | +| isfromtemplateinstantiation.cpp:53:46:53:46 | definition of u | I | | Definition | | | isfromtemplateinstantiation.cpp:53:46:53:46 | u | | T | Declaration | | | isfromtemplateinstantiation.cpp:53:46:53:46 | u | I | | Declaration | | | isfromtemplateinstantiation.cpp:53:46:53:46 | u | I | T | Declaration | | @@ -390,8 +414,10 @@ isFromUninstantiatedTemplate | isfromtemplateinstantiation.cpp:86:16:86:16 | l | I | | Declaration | | | isfromtemplateinstantiation.cpp:90:3:90:18 | MyClassEnumConst | | T | Declaration | | | isfromtemplateinstantiation.cpp:90:3:90:18 | MyClassEnumConst | I | | Declaration | | +| isfromtemplateinstantiation.cpp:93:7:93:7 | myMethod1 | I | | Declaration | | | isfromtemplateinstantiation.cpp:93:7:93:15 | myMethod1 | | T | Declaration | | -| isfromtemplateinstantiation.cpp:93:7:93:15 | myMethod1 | I | | Declaration | | +| isfromtemplateinstantiation.cpp:93:29:93:32 | definition of mce1 | | T | Definition | | +| isfromtemplateinstantiation.cpp:93:29:93:32 | definition of mce1 | I | | Definition | | | isfromtemplateinstantiation.cpp:93:29:93:32 | mce1 | | T | Declaration | | | isfromtemplateinstantiation.cpp:93:29:93:32 | mce1 | I | | Declaration | | | isfromtemplateinstantiation.cpp:93:54:93:55 | { ... } | | T | Stmt | | @@ -399,7 +425,12 @@ isFromUninstantiatedTemplate | isfromtemplateinstantiation.cpp:93:55:93:55 | return ... | | T | Stmt | | | isfromtemplateinstantiation.cpp:93:55:93:55 | return ... | I | | Stmt | | | isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | | T | Declaration | | -| isfromtemplateinstantiation.cpp:97:25:97:60 | myMethod2 | I | | Declaration | | +| isfromtemplateinstantiation.cpp:97:52:97:52 | definition of myMethod2 | | T | Definition | | +| isfromtemplateinstantiation.cpp:97:52:97:52 | definition of myMethod2 | I | | Definition | | +| isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | | T | Declaration | | +| isfromtemplateinstantiation.cpp:97:52:97:52 | myMethod2 | I | | Declaration | | +| isfromtemplateinstantiation.cpp:97:74:97:77 | definition of mce2 | | T | Definition | | +| isfromtemplateinstantiation.cpp:97:74:97:77 | definition of mce2 | | T | Definition | | | isfromtemplateinstantiation.cpp:97:74:97:77 | mce2 | | T | Declaration | | | isfromtemplateinstantiation.cpp:97:74:97:77 | mce2 | I | | Declaration | | | isfromtemplateinstantiation.cpp:98:1:99:1 | { ... } | | T | Stmt | | @@ -417,13 +448,6 @@ isFromUninstantiatedTemplate | isfromtemplateinstantiation.cpp:128:7:128:30 | AnotherTemplateClass | | T | Declaration | | | isfromtemplateinstantiation.cpp:128:7:128:30 | AnotherTemplateClass | I | | Declaration | | | isfromtemplateinstantiation.cpp:129:6:129:6 | f | | T | Declaration | | -| isfromtemplateinstantiation.cpp:129:6:129:6 | f | I | | Declaration | | -| isfromtemplateinstantiation.cpp:129:10:129:22 | { ... } | | T | Stmt | | -| isfromtemplateinstantiation.cpp:129:10:129:22 | { ... } | I | | Stmt | | -| isfromtemplateinstantiation.cpp:129:12:129:20 | return ... | | T | Stmt | | -| isfromtemplateinstantiation.cpp:129:12:129:20 | return ... | I | | Stmt | | -| isfromtemplateinstantiation.cpp:129:19:129:19 | 1 | | T | Expr | | -| isfromtemplateinstantiation.cpp:129:19:129:19 | 1 | I | | Expr | | | isfromtemplateinstantiation.cpp:134:29:134:29 | declaration of operator= | I | | DeclarationEntry | | | isfromtemplateinstantiation.cpp:134:29:134:29 | declaration of operator= | I | | DeclarationEntry | | | isfromtemplateinstantiation.cpp:134:29:134:29 | operator= | I | | Declaration | | @@ -435,6 +459,7 @@ isFromUninstantiatedTemplate | isfromtemplateinstantiation.cpp:135:31:135:31 | operator= | I | T | Declaration | | | isfromtemplateinstantiation.cpp:135:31:135:31 | operator= | I | T | Declaration | | | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | | T | Declaration | | +| isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | I | T | Declaration | | | isfromtemplateinstantiation.cpp:135:31:135:35 | Inner | I | T | Declaration | | | isfromtemplateinstantiation.cpp:136:7:136:7 | definition of x | | T | Definition | | | isfromtemplateinstantiation.cpp:136:7:136:7 | definition of x | I | T | Definition | | @@ -472,72 +497,19 @@ isFromUninstantiatedTemplate | load.cpp:15:14:15:15 | is | | T | Declaration | | | load.cpp:15:14:15:15 | is | I | | Declaration | | | load.cpp:18:5:18:25 | basic_text_iprimitive | | T | Declaration | | -| load.cpp:18:5:18:25 | basic_text_iprimitive | I | | Declaration | | -| load.cpp:18:36:18:42 | isParam | | T | Declaration | | -| load.cpp:18:36:18:42 | isParam | I | | Declaration | | -| load.cpp:19:11:19:21 | constructor init of field is | | T | Expr | | -| load.cpp:19:11:19:21 | constructor init of field is | I | | Expr | | | load.cpp:19:14:19:20 | (reference dereference) | | T | Expr | | -| load.cpp:19:14:19:20 | (reference dereference) | I | | Expr | | | load.cpp:19:14:19:20 | (reference to) | | T | Expr | | -| load.cpp:19:14:19:20 | (reference to) | I | | Expr | | | load.cpp:19:14:19:20 | isParam | | T | Expr | Ref | -| load.cpp:19:14:19:20 | isParam | I | | Expr | Ref | -| load.cpp:19:23:19:24 | { ... } | | T | Stmt | | -| load.cpp:19:23:19:24 | { ... } | I | | Stmt | | -| load.cpp:19:24:19:24 | return ... | | T | Stmt | | -| load.cpp:19:24:19:24 | return ... | I | | Stmt | | | load.cpp:22:10:22:13 | load | | T | Declaration | | -| load.cpp:22:10:22:13 | load | I | | Declaration | | | load.cpp:22:10:22:13 | load | I | T | Declaration | | | load.cpp:22:19:22:19 | t | | T | Declaration | | -| load.cpp:22:19:22:19 | t | I | | Declaration | | | load.cpp:22:19:22:19 | t | I | T | Declaration | | -| load.cpp:23:5:25:5 | { ... } | | T | Stmt | | -| load.cpp:23:5:25:5 | { ... } | I | | Stmt | | | load.cpp:24:9:24:10 | (reference dereference) | | T | Expr | | -| load.cpp:24:9:24:10 | (reference dereference) | I | | Expr | | | load.cpp:24:9:24:10 | is | | T | Expr | Not ref | -| load.cpp:24:9:24:10 | is | I | | Expr | Not ref | -| load.cpp:24:9:24:16 | ExprStmt | | T | Stmt | | -| load.cpp:24:9:24:16 | ExprStmt | I | | Stmt | | | load.cpp:24:15:24:15 | (reference dereference) | | T | Expr | | -| load.cpp:24:15:24:15 | (reference dereference) | I | | Expr | | -| load.cpp:24:15:24:15 | (reference to) | I | | Expr | | | load.cpp:24:15:24:15 | t | | T | Expr | Not ref | -| load.cpp:24:15:24:15 | t | I | | Expr | Ref | -| load.cpp:25:5:25:5 | return ... | | T | Stmt | | -| load.cpp:25:5:25:5 | return ... | I | | Stmt | | | load.cpp:27:10:27:13 | load | | T | Declaration | | -| load.cpp:27:10:27:13 | load | I | | Declaration | | -| load.cpp:27:22:27:22 | t | | T | Declaration | | -| load.cpp:27:22:27:22 | t | I | | Declaration | | -| load.cpp:28:5:32:5 | { ... } | | T | Stmt | | -| load.cpp:28:5:32:5 | { ... } | I | | Stmt | | -| load.cpp:29:9:29:20 | declaration | | T | Stmt | | -| load.cpp:29:9:29:20 | declaration | I | | Stmt | | -| load.cpp:29:19:29:19 | definition of i | | T | Definition | | -| load.cpp:29:19:29:19 | definition of i | I | | Definition | | -| load.cpp:29:19:29:19 | i | | T | Declaration | | -| load.cpp:29:19:29:19 | i | I | | Declaration | | -| load.cpp:30:9:30:12 | Unknown literal | | T | Expr | | -| load.cpp:30:9:30:12 | call to load | I | | Expr | | -| load.cpp:30:9:30:16 | ExprStmt | | T | Stmt | | -| load.cpp:30:9:30:16 | ExprStmt | I | | Stmt | | -| load.cpp:30:14:30:14 | (reference to) | I | | Expr | | -| load.cpp:30:14:30:14 | i | | T | Expr | Not ref | -| load.cpp:30:14:30:14 | i | I | | Expr | Ref | | load.cpp:31:9:31:9 | (reference dereference) | | T | Expr | | -| load.cpp:31:9:31:9 | (reference dereference) | I | | Expr | | | load.cpp:31:9:31:9 | t | | T | Expr | Not ref | -| load.cpp:31:9:31:9 | t | I | | Expr | Not ref | -| load.cpp:31:9:31:13 | ... = ... | | T | Expr | | -| load.cpp:31:9:31:13 | ... = ... | I | | Expr | | -| load.cpp:31:9:31:14 | ExprStmt | | T | Stmt | | -| load.cpp:31:9:31:14 | ExprStmt | I | | Stmt | | | load.cpp:31:13:31:13 | (char)... | | T | Expr | | -| load.cpp:31:13:31:13 | (char)... | I | | Expr | | | load.cpp:31:13:31:13 | i | | T | Expr | Not ref | -| load.cpp:31:13:31:13 | i | I | | Expr | Not ref | -| load.cpp:32:5:32:5 | return ... | | T | Stmt | | -| load.cpp:32:5:32:5 | return ... | I | | Stmt | | diff --git a/cpp/ql/test/library-tests/templates/switch/test.expected b/cpp/ql/test/library-tests/templates/switch/test.expected index b4124494ffeb..1ba49e1caf9d 100644 --- a/cpp/ql/test/library-tests/templates/switch/test.expected +++ b/cpp/ql/test/library-tests/templates/switch/test.expected @@ -1,2 +1 @@ | test.cpp:13:3:20:3 | switch (...) ... | 3 | -| test.cpp:13:3:20:3 | switch (...) ... | 3 | diff --git a/cpp/ql/test/library-tests/templates/type_instantiations/types.expected b/cpp/ql/test/library-tests/templates/type_instantiations/types.expected index 31d7b7caa944..94930e59b7fa 100644 --- a/cpp/ql/test/library-tests/templates/type_instantiations/types.expected +++ b/cpp/ql/test/library-tests/templates/type_instantiations/types.expected @@ -19,11 +19,14 @@ | file://:0:0:0:0 | __int128 | | file://:0:0:0:0 | __va_list_tag | | file://:0:0:0:0 | __va_list_tag & | +| file://:0:0:0:0 | __va_list_tag && | | file://:0:0:0:0 | auto | | file://:0:0:0:0 | bool | | file://:0:0:0:0 | char | | file://:0:0:0:0 | char16_t | | file://:0:0:0:0 | char32_t | +| file://:0:0:0:0 | const __va_list_tag | +| file://:0:0:0:0 | const __va_list_tag & | | file://:0:0:0:0 | decltype(nullptr) | | file://:0:0:0:0 | double | | file://:0:0:0:0 | error | diff --git a/cpp/ql/test/library-tests/type_sizes/type_sizes.expected b/cpp/ql/test/library-tests/type_sizes/type_sizes.expected index 12a38db4f930..287a713f2f09 100644 --- a/cpp/ql/test/library-tests/type_sizes/type_sizes.expected +++ b/cpp/ql/test/library-tests/type_sizes/type_sizes.expected @@ -40,6 +40,7 @@ | file://:0:0:0:0 | __int128 | 16 | | file://:0:0:0:0 | __va_list_tag | 24 | | file://:0:0:0:0 | __va_list_tag & | 8 | +| file://:0:0:0:0 | __va_list_tag && | 8 | | file://:0:0:0:0 | auto | | | file://:0:0:0:0 | bool | 1 | | file://:0:0:0:0 | char | 1 | @@ -54,13 +55,15 @@ | file://:0:0:0:0 | const StructWithDef & | 8 | | file://:0:0:0:0 | const UnionWithDef | 4 | | file://:0:0:0:0 | const UnionWithDef & | 8 | +| file://:0:0:0:0 | const __va_list_tag | 24 | +| file://:0:0:0:0 | const __va_list_tag & | 8 | | file://:0:0:0:0 | const char | 1 | | file://:0:0:0:0 | const char * | 8 | | file://:0:0:0:0 | const char *const | 8 | | file://:0:0:0:0 | const char[5] | 5 | | file://:0:0:0:0 | decltype(nullptr) | 8 | | file://:0:0:0:0 | double | 8 | -| file://:0:0:0:0 | error | 0 | +| file://:0:0:0:0 | error | 1 | | file://:0:0:0:0 | float | 4 | | file://:0:0:0:0 | int | 4 | | file://:0:0:0:0 | int & | 8 | @@ -78,7 +81,7 @@ | file://:0:0:0:0 | signed long | 8 | | file://:0:0:0:0 | signed long long | 8 | | file://:0:0:0:0 | signed short | 2 | -| file://:0:0:0:0 | unknown | 0 | +| file://:0:0:0:0 | unknown | 1 | | file://:0:0:0:0 | unsigned __int128 | 16 | | file://:0:0:0:0 | unsigned char | 1 | | file://:0:0:0:0 | unsigned int | 4 | diff --git a/cpp/ql/test/library-tests/types/types/Types.expected b/cpp/ql/test/library-tests/types/types/Types.expected index 63feb266b6ee..ff44058f8a79 100644 --- a/cpp/ql/test/library-tests/types/types/Types.expected +++ b/cpp/ql/test/library-tests/types/types/Types.expected @@ -1,6 +1,8 @@ | file://:0:0:0:0 | fp_offset | | file://:0:0:0:0 | unsigned int | IntType | | file://:0:0:0:0 | gp_offset | | file://:0:0:0:0 | unsigned int | IntType | | file://:0:0:0:0 | overflow_arg_area | | file://:0:0:0:0 | void * | PointerType, VoidPointerType, base: void | +| file://:0:0:0:0 | p#0 | Parameter | file://:0:0:0:0 | __va_list_tag && | ReferenceType, base: __va_list_tag | +| file://:0:0:0:0 | p#0 | Parameter | file://:0:0:0:0 | const __va_list_tag & | ReferenceType, base: const __va_list_tag | | file://:0:0:0:0 | reg_save_area | | file://:0:0:0:0 | void * | PointerType, VoidPointerType, base: void | | types.cpp:1:12:1:12 | i | | file://:0:0:0:0 | int | IntType | | types.cpp:3:11:3:11 | c | isConst | file://:0:0:0:0 | const int | base: int, isConst | diff --git a/cpp/ql/test/library-tests/unnamed/elements.expected b/cpp/ql/test/library-tests/unnamed/elements.expected index 4aca4dd6877d..8871ae29bfac 100644 --- a/cpp/ql/test/library-tests/unnamed/elements.expected +++ b/cpp/ql/test/library-tests/unnamed/elements.expected @@ -36,7 +36,6 @@ | file://:0:0:0:0 | char32_t | Other | | file://:0:0:0:0 | const | Other | | file://:0:0:0:0 | decltype(nullptr) | Other | -| file://:0:0:0:0 | definition of __va_list_tag | Other | | file://:0:0:0:0 | definition of fp_offset | Other | | file://:0:0:0:0 | definition of gp_offset | Other | | file://:0:0:0:0 | definition of overflow_arg_area | Other | diff --git a/cpp/ql/test/library-tests/unspecified_type/types/unspecified_type.expected b/cpp/ql/test/library-tests/unspecified_type/types/unspecified_type.expected index 60eb41ea55f8..d9407d2b67e9 100644 --- a/cpp/ql/test/library-tests/unspecified_type/types/unspecified_type.expected +++ b/cpp/ql/test/library-tests/unspecified_type/types/unspecified_type.expected @@ -20,11 +20,14 @@ | file://:0:0:0:0 | __float128 | __float128 | | file://:0:0:0:0 | __int128 | __int128 | | file://:0:0:0:0 | __va_list_tag & | __va_list_tag & | +| file://:0:0:0:0 | __va_list_tag && | __va_list_tag && | | file://:0:0:0:0 | auto | auto | | file://:0:0:0:0 | bool | bool | | file://:0:0:0:0 | char | char | | file://:0:0:0:0 | char16_t | char16_t | | file://:0:0:0:0 | char32_t | char32_t | +| file://:0:0:0:0 | const __va_list_tag | __va_list_tag | +| file://:0:0:0:0 | const __va_list_tag & | __va_list_tag & | | file://:0:0:0:0 | decltype(nullptr) | decltype(nullptr) | | file://:0:0:0:0 | double | double | | file://:0:0:0:0 | error | error | diff --git a/cpp/ql/test/library-tests/unspecified_type/unspecified_type/strip_top_level.expected b/cpp/ql/test/library-tests/unspecified_type/unspecified_type/strip_top_level.expected index e6b2fd63da63..6cc7e55a9ee6 100644 --- a/cpp/ql/test/library-tests/unspecified_type/unspecified_type/strip_top_level.expected +++ b/cpp/ql/test/library-tests/unspecified_type/unspecified_type/strip_top_level.expected @@ -1,6 +1,8 @@ | file://:0:0:0:0 | fp_offset | unsigned int | unsigned int | | file://:0:0:0:0 | gp_offset | unsigned int | unsigned int | | file://:0:0:0:0 | overflow_arg_area | pointer to {void} | pointer to {void} | +| file://:0:0:0:0 | p#0 | reference to {const {struct __va_list_tag}} | reference to {const {struct __va_list_tag}} | +| file://:0:0:0:0 | p#0 | rvalue reference to {struct __va_list_tag} | rvalue reference to {struct __va_list_tag} | | file://:0:0:0:0 | reg_save_area | pointer to {void} | pointer to {void} | | unspecified_type.cpp:2:5:2:7 | vv1 | int | int | | unspecified_type.cpp:3:11:3:13 | vv2 | const {int} | int | diff --git a/cpp/ql/test/library-tests/unspecified_type/unspecified_type/unspecified_type.expected b/cpp/ql/test/library-tests/unspecified_type/unspecified_type/unspecified_type.expected index fbdb040c7b3a..25fc70618b65 100644 --- a/cpp/ql/test/library-tests/unspecified_type/unspecified_type/unspecified_type.expected +++ b/cpp/ql/test/library-tests/unspecified_type/unspecified_type/unspecified_type.expected @@ -1,6 +1,8 @@ | file://:0:0:0:0 | fp_offset | unsigned int | unsigned int | | file://:0:0:0:0 | gp_offset | unsigned int | unsigned int | | file://:0:0:0:0 | overflow_arg_area | pointer to {void} | pointer to {void} | +| file://:0:0:0:0 | p#0 | reference to {const {struct __va_list_tag}} | reference to {struct __va_list_tag} | +| file://:0:0:0:0 | p#0 | rvalue reference to {struct __va_list_tag} | rvalue reference to {struct __va_list_tag} | | file://:0:0:0:0 | reg_save_area | pointer to {void} | pointer to {void} | | unspecified_type.cpp:2:5:2:7 | vv1 | int | int | | unspecified_type.cpp:3:11:3:13 | vv2 | const {int} | int | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index c050ac2bffd0..656a4c888f62 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -67,10 +67,10 @@ | test.cpp:156:3:156:9 | 0 | 156:c14-c20 156:c3-c9 157:c10-c16 | | test.cpp:171:3:171:6 | (int)... | 171:c3-c6 172:c3-c6 | | test.cpp:171:3:171:6 | e1x1 | 171:c3-c6 172:c3-c6 | -| test.cpp:179:10:179:22 | (...) | 179:c10-c22 179:c10-c22 | -| test.cpp:179:17:179:17 | y | 179:c17-c17 179:c17-c17 | -| test.cpp:179:17:179:21 | ... + ... | 179:c17-c21 179:c17-c21 | -| test.cpp:185:17:185:17 | y | 185:c17-c17 185:c17-c17 | +| test.cpp:179:10:179:22 | (...) | 179:c10-c22 | +| test.cpp:179:17:179:17 | y | 179:c17-c17 | +| test.cpp:179:17:179:21 | ... + ... | 179:c17-c21 | +| test.cpp:185:17:185:17 | y | 185:c17-c17 | | test.cpp:202:3:202:18 | sizeof(padded_t) | 202:c3-c18 204:c3-c18 | | test.cpp:205:24:205:34 | sizeof(int) | 205:c24-c34 245:c25-c35 246:c25-c35 | | test.cpp:206:3:206:21 | alignof(int_holder) | 206:c25-c43 206:c3-c21 | diff --git a/cpp/ql/test/library-tests/variables/variables/types.expected b/cpp/ql/test/library-tests/variables/variables/types.expected index 2cdacdc8608a..f239d2768715 100644 --- a/cpp/ql/test/library-tests/variables/variables/types.expected +++ b/cpp/ql/test/library-tests/variables/variables/types.expected @@ -20,6 +20,7 @@ | __int128 | Int128Type | | | | | | __va_list_tag | DirectAccessHolder, MetricClass, Struct, StructLikeClass | | | | | | __va_list_tag & | LValueReferenceType | | __va_list_tag | | | +| __va_list_tag && | RValueReferenceType | | __va_list_tag | | | | address | DirectAccessHolder, MetricClass, Struct, StructLikeClass | | | | | | address & | LValueReferenceType | | address | | | | address && | RValueReferenceType | | address | | | @@ -41,6 +42,8 @@ | char[10] | ArrayType | char | char | | | | char[53] | ArrayType | char | char | | | | char[] | ArrayType | char | char | | | +| const __va_list_tag | SpecifiedType | | __va_list_tag | | | +| const __va_list_tag & | LValueReferenceType | | const __va_list_tag | | | | const address | SpecifiedType | | address | | | | const address & | LValueReferenceType | | const address | | | | const char | SpecifiedType | | char | | | diff --git a/cpp/ql/test/library-tests/variables/variables/variable.expected b/cpp/ql/test/library-tests/variables/variables/variable.expected index d74162db4401..0599d8400687 100644 --- a/cpp/ql/test/library-tests/variables/variables/variable.expected +++ b/cpp/ql/test/library-tests/variables/variables/variable.expected @@ -1,8 +1,12 @@ | file://:0:0:0:0 | fp_offset | file://:0:0:0:0 | unsigned int | Field | | | | file://:0:0:0:0 | gp_offset | file://:0:0:0:0 | unsigned int | Field | | | | file://:0:0:0:0 | overflow_arg_area | file://:0:0:0:0 | void * | Field | | | +| file://:0:0:0:0 | p#0 | file://:0:0:0:0 | __va_list_tag && | SemanticStackVariable | | | +| file://:0:0:0:0 | p#0 | file://:0:0:0:0 | __va_list_tag && | StackVariable | | | | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | address && | SemanticStackVariable | | | | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | address && | StackVariable | | | +| file://:0:0:0:0 | p#0 | file://:0:0:0:0 | const __va_list_tag & | SemanticStackVariable | | | +| file://:0:0:0:0 | p#0 | file://:0:0:0:0 | const __va_list_tag & | StackVariable | | | | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | const address & | SemanticStackVariable | | | | file://:0:0:0:0 | p#0 | file://:0:0:0:0 | const address & | StackVariable | | | | file://:0:0:0:0 | reg_save_area | file://:0:0:0:0 | void * | Field | | | diff --git a/cpp/ql/test/library-tests/vector_types/builtin_ops.expected b/cpp/ql/test/library-tests/vector_types/builtin_ops.expected index 84950bc9b213..2c0dd9d00173 100644 --- a/cpp/ql/test/library-tests/vector_types/builtin_ops.expected +++ b/cpp/ql/test/library-tests/vector_types/builtin_ops.expected @@ -1 +1,2 @@ | vector_types.cpp:31:13:31:49 | __builtin_shufflevector | +| vector_types.cpp:58:10:58:52 | __builtin_convertvector | diff --git a/cpp/ql/test/library-tests/vector_types/fill.expected b/cpp/ql/test/library-tests/vector_types/fill.expected new file mode 100644 index 000000000000..28284295c23c --- /dev/null +++ b/cpp/ql/test/library-tests/vector_types/fill.expected @@ -0,0 +1 @@ +| vector_types.cpp:51:18:51:18 | (vector fill) ... | file://:0:0:0:0 | __attribute((vector_size(16))) int | vector_types.cpp:51:18:51:18 | n | file://:0:0:0:0 | int | diff --git a/cpp/ql/test/library-tests/vector_types/fill.ql b/cpp/ql/test/library-tests/vector_types/fill.ql new file mode 100644 index 000000000000..8415df779297 --- /dev/null +++ b/cpp/ql/test/library-tests/vector_types/fill.ql @@ -0,0 +1,5 @@ +import cpp + +from VectorFillOperation vf, Expr operand +where operand = vf.getOperand() +select vf, vf.getType(), operand, operand.getType() diff --git a/cpp/ql/test/library-tests/vector_types/variables.expected b/cpp/ql/test/library-tests/vector_types/variables.expected index 2182120cf395..225d18dd9afb 100644 --- a/cpp/ql/test/library-tests/vector_types/variables.expected +++ b/cpp/ql/test/library-tests/vector_types/variables.expected @@ -1,6 +1,17 @@ | file://:0:0:0:0 | fp_offset | fp_offset | file://:0:0:0:0 | unsigned int | 4 | | file://:0:0:0:0 | gp_offset | gp_offset | file://:0:0:0:0 | unsigned int | 4 | | file://:0:0:0:0 | overflow_arg_area | overflow_arg_area | file://:0:0:0:0 | void * | 8 | +| file://:0:0:0:0 | p#0 | p#0 | file://:0:0:0:0 | __attribute((vector_size(16))) double | 16 | +| file://:0:0:0:0 | p#0 | p#0 | file://:0:0:0:0 | __attribute((vector_size(16))) float | 16 | +| file://:0:0:0:0 | p#0 | p#0 | file://:0:0:0:0 | __va_list_tag && | 8 | +| file://:0:0:0:0 | p#0 | p#0 | file://:0:0:0:0 | const __va_list_tag & | 8 | +| file://:0:0:0:0 | p#0 | p#0 | file://:0:0:0:0 | double * | 8 | +| file://:0:0:0:0 | p#0 | p#0 | file://:0:0:0:0 | float * | 8 | +| file://:0:0:0:0 | p#1 | p#1 | file://:0:0:0:0 | __attribute((vector_size(16))) double | 16 | +| file://:0:0:0:0 | p#1 | p#1 | file://:0:0:0:0 | __attribute((vector_size(16))) double | 16 | +| file://:0:0:0:0 | p#1 | p#1 | file://:0:0:0:0 | __attribute((vector_size(16))) float | 16 | +| file://:0:0:0:0 | p#1 | p#1 | file://:0:0:0:0 | __attribute((vector_size(16))) float | 16 | +| file://:0:0:0:0 | p#2 | p#2 | file://:0:0:0:0 | char | 1 | | file://:0:0:0:0 | reg_save_area | reg_save_area | file://:0:0:0:0 | void * | 8 | | vector_types.cpp:9:21:9:21 | x | x | vector_types.cpp:6:15:6:17 | v4f | 16 | | vector_types.cpp:14:18:14:20 | lhs | lhs | vector_types.cpp:6:15:6:17 | v4f | 16 | @@ -14,3 +25,7 @@ | vector_types.cpp:33:8:33:9 | v5 | v5 | file://:0:0:0:0 | __attribute((vector_size(16))) double | 16 | | vector_types.cpp:34:10:34:16 | doubles | doubles | file://:0:0:0:0 | double[2] | 16 | | vector_types.cpp:41:14:41:16 | arg | arg | vector_types.cpp:7:14:7:17 | v16c | 16 | +| vector_types.cpp:47:23:47:25 | dst | dst | file://:0:0:0:0 | v16i * | 8 | +| vector_types.cpp:47:34:47:36 | src | src | file://:0:0:0:0 | v16i * | 8 | +| vector_types.cpp:47:43:47:43 | n | n | file://:0:0:0:0 | int | 4 | +| vector_types.cpp:57:43:57:44 | vf | vf | vector_types.cpp:55:16:55:27 | vector4float | 16 | diff --git a/cpp/ql/test/library-tests/vector_types/vector_ops.expected b/cpp/ql/test/library-tests/vector_types/vector_ops.expected index c7ea5dd02d42..376457b88c84 100644 --- a/cpp/ql/test/library-tests/vector_types/vector_ops.expected +++ b/cpp/ql/test/library-tests/vector_types/vector_ops.expected @@ -1,2 +1,4 @@ | vector_types.cpp:16:16:16:41 | ... == ... | == | file://:0:0:0:0 | __attribute((vector_size(16))) char | | vector_types.cpp:21:10:21:18 | ... < ... | < | file://:0:0:0:0 | __attribute((vector_size(16))) int | +| vector_types.cpp:51:10:51:18 | ... << ... | << | file://:0:0:0:0 | __attribute((vector_size(16))) int | +| vector_types.cpp:51:18:51:18 | (vector fill) ... | (vector fill) | file://:0:0:0:0 | __attribute((vector_size(16))) int | diff --git a/cpp/ql/test/library-tests/vector_types/vector_types.cpp b/cpp/ql/test/library-tests/vector_types/vector_types.cpp index 8429716642eb..5e875b6b9539 100644 --- a/cpp/ql/test/library-tests/vector_types/vector_types.cpp +++ b/cpp/ql/test/library-tests/vector_types/vector_types.cpp @@ -41,3 +41,19 @@ int main() { v4f lax(v16c arg) { return arg; } + +typedef int v16i __attribute__((vector_size(16))); + +void shift_left(v16i *dst, v16i *src, int n) { + // We represent this shift as an operation on vector types, and the + // right-hand side is a vector fill expression (i.e. a vector filled with n in + // each element). + *dst = *src << n; +} + +typedef double vector4double __attribute__((__vector_size__(32))); +typedef float vector4float __attribute__((__vector_size__(16))); + +vector4double convert_vector(vector4float vf) { + return __builtin_convertvector(vf, vector4double); +} diff --git a/cpp/ql/test/library-tests/virtual_functions/cfg/cfg.expected b/cpp/ql/test/library-tests/virtual_functions/cfg/cfg.expected index fae05c1383f2..9d0e0c7773a7 100644 --- a/cpp/ql/test/library-tests/virtual_functions/cfg/cfg.expected +++ b/cpp/ql/test/library-tests/virtual_functions/cfg/cfg.expected @@ -1,149 +1,149 @@ -| Base::Base | false | 137 | 137 | Base | -| Base::Base | false | 140 | 140 | return ... | -| Base::Base | false | 142 | 142 | { ... } | -| Base::Base | false | 156 | 156 | Base | -| Base::Base | false | 158 | 158 | Base | -| Base::Base | true | 140 | 137 | | -| Base::Base | true | 142 | 140 | | -| Base::Base_f | false | 80 | 80 | Base_f | -| Base::Base_f | false | 86 | 86 | call to f | -| Base::Base_f | false | 89 | 89 | this | -| Base::Base_f | false | 90 | 90 | initializer for i | -| Base::Base_f | false | 93 | 93 | declaration | -| Base::Base_f | false | 97 | 97 | 1 | -| Base::Base_f | false | 98 | 98 | return ... | -| Base::Base_f | false | 100 | 100 | { ... } | -| Base::Base_f | true | 89 | 86 | | -| Base::Base_f | true | 90 | 89 | | -| Base::Base_f | true | 93 | 90 | | -| Base::Base_f | true | 97 | 80 | | -| Base::Base_f | true | 98 | 97 | | -| Base::Base_f | true | 100 | 93 | | -| Base::Base_g | false | 114 | 114 | Base_g | -| Base::Base_g | false | 120 | 120 | call to g | -| Base::Base_g | false | 123 | 123 | this | -| Base::Base_g | false | 124 | 124 | initializer for i | -| Base::Base_g | false | 127 | 127 | declaration | -| Base::Base_g | false | 131 | 131 | 4 | -| Base::Base_g | false | 132 | 132 | return ... | -| Base::Base_g | false | 134 | 134 | { ... } | -| Base::Base_g | true | 120 | 132 | | -| Base::Base_g | true | 123 | 120 | | -| Base::Base_g | true | 124 | 123 | | -| Base::Base_g | true | 127 | 124 | | -| Base::Base_g | true | 131 | 114 | | -| Base::Base_g | true | 132 | 131 | | -| Base::Base_g | true | 134 | 127 | | -| Base::f | false | 68 | 68 | f | -| Base::f | false | 71 | 71 | call to abort | -| Base::f | false | 73 | 73 | ExprStmt | -| Base::f | false | 75 | 75 | return ... | -| Base::f | false | 77 | 77 | { ... } | -| Base::f | true | 73 | 71 | | -| Base::f | true | 75 | 68 | | -| Base::f | true | 77 | 73 | | -| Base::g | false | 103 | 103 | g | -| Base::g | false | 108 | 108 | 3 | -| Base::g | false | 109 | 109 | return ... | -| Base::g | false | 111 | 111 | { ... } | -| Base::g | true | 108 | 103 | | -| Base::g | true | 109 | 108 | | -| Base::g | true | 111 | 109 | | -| Base::operator= | false | 148 | 148 | operator= | -| Base::operator= | false | 154 | 154 | operator= | -| __va_list_tag::operator= | false | 54 | 54 | operator= | -| __va_list_tag::operator= | false | 60 | 60 | operator= | -| abort | false | 64 | 64 | abort | -| fun_f1 | false | 170 | 170 | fun_f1 | -| fun_f1 | false | 179 | 179 | call to Base | -| fun_f1 | false | 180 | 180 | new | -| fun_f1 | false | 182 | 182 | initializer for p1 | -| fun_f1 | false | 186 | 186 | declaration | -| fun_f1 | false | 189 | 189 | call to f | -| fun_f1 | false | 191 | 191 | p1 | -| fun_f1 | false | 193 | 193 | initializer for i | -| fun_f1 | false | 197 | 197 | declaration | -| fun_f1 | false | 201 | 201 | 2 | -| fun_f1 | false | 202 | 202 | return ... | -| fun_f1 | false | 204 | 204 | { ... } | -| fun_f1 | true | 179 | 180 | | -| fun_f1 | true | 180 | 197 | | -| fun_f1 | true | 182 | 179 | | -| fun_f1 | true | 186 | 182 | | -| fun_f1 | true | 189 | 202 | | -| fun_f1 | true | 191 | 189 | | -| fun_f1 | true | 193 | 191 | | -| fun_f1 | true | 197 | 193 | | -| fun_f1 | true | 201 | 170 | | -| fun_f1 | true | 202 | 201 | | -| fun_f1 | true | 204 | 186 | | -| fun_f2 | false | 207 | 207 | fun_f2 | -| fun_f2 | false | 211 | 211 | call to Base | -| fun_f2 | false | 212 | 212 | new | -| fun_f2 | false | 214 | 214 | initializer for p1 | -| fun_f2 | false | 218 | 218 | declaration | -| fun_f2 | false | 223 | 223 | call to f | -| fun_f2 | false | 225 | 225 | p1 | -| fun_f2 | false | 227 | 227 | initializer for i | -| fun_f2 | false | 231 | 231 | declaration | -| fun_f2 | false | 235 | 235 | 2 | -| fun_f2 | false | 236 | 236 | return ... | -| fun_f2 | false | 238 | 238 | { ... } | -| fun_f2 | true | 211 | 212 | | -| fun_f2 | true | 212 | 231 | | -| fun_f2 | true | 214 | 211 | | -| fun_f2 | true | 218 | 214 | | -| fun_f2 | true | 225 | 223 | | -| fun_f2 | true | 227 | 225 | | -| fun_f2 | true | 231 | 227 | | -| fun_f2 | true | 235 | 207 | | -| fun_f2 | true | 236 | 235 | | -| fun_f2 | true | 238 | 218 | | -| fun_g1 | false | 241 | 241 | fun_g1 | -| fun_g1 | false | 245 | 245 | call to Base | -| fun_g1 | false | 246 | 246 | new | -| fun_g1 | false | 248 | 248 | initializer for p1 | -| fun_g1 | false | 252 | 252 | declaration | -| fun_g1 | false | 255 | 255 | call to g | -| fun_g1 | false | 257 | 257 | p1 | -| fun_g1 | false | 259 | 259 | initializer for i | -| fun_g1 | false | 263 | 263 | declaration | -| fun_g1 | false | 267 | 267 | 2 | -| fun_g1 | false | 268 | 268 | return ... | -| fun_g1 | false | 270 | 270 | { ... } | -| fun_g1 | true | 245 | 246 | | -| fun_g1 | true | 246 | 263 | | -| fun_g1 | true | 248 | 245 | | -| fun_g1 | true | 252 | 248 | | -| fun_g1 | true | 255 | 268 | | -| fun_g1 | true | 257 | 255 | | -| fun_g1 | true | 259 | 257 | | -| fun_g1 | true | 263 | 259 | | -| fun_g1 | true | 267 | 241 | | -| fun_g1 | true | 268 | 267 | | -| fun_g1 | true | 270 | 252 | | -| fun_g2 | false | 273 | 273 | fun_g2 | -| fun_g2 | false | 277 | 277 | call to Base | -| fun_g2 | false | 278 | 278 | new | -| fun_g2 | false | 280 | 280 | initializer for p1 | -| fun_g2 | false | 284 | 284 | declaration | -| fun_g2 | false | 289 | 289 | call to g | -| fun_g2 | false | 291 | 291 | p1 | -| fun_g2 | false | 293 | 293 | initializer for i | -| fun_g2 | false | 297 | 297 | declaration | -| fun_g2 | false | 301 | 301 | 2 | -| fun_g2 | false | 302 | 302 | return ... | -| fun_g2 | false | 304 | 304 | { ... } | -| fun_g2 | true | 277 | 278 | | -| fun_g2 | true | 278 | 297 | | -| fun_g2 | true | 280 | 277 | | -| fun_g2 | true | 284 | 280 | | -| fun_g2 | true | 289 | 302 | | -| fun_g2 | true | 291 | 289 | | -| fun_g2 | true | 293 | 291 | | -| fun_g2 | true | 297 | 293 | | -| fun_g2 | true | 301 | 273 | | -| fun_g2 | true | 302 | 301 | | -| fun_g2 | true | 304 | 284 | | -| operator delete | false | 177 | 177 | operator delete | -| operator new | false | 174 | 174 | operator new | +| Base::Base | false | 215 | 215 | Base | +| Base::Base | false | 220 | 220 | return ... | +| Base::Base | false | 222 | 222 | { ... } | +| Base::Base | false | 363 | 363 | Base | +| Base::Base | false | 367 | 367 | Base | +| Base::Base | true | 220 | 215 | | +| Base::Base | true | 222 | 220 | | +| Base::Base_f | false | 407 | 407 | Base_f | +| Base::Base_f | false | 412 | 412 | declaration | +| Base::Base_f | false | 416 | 416 | 1 | +| Base::Base_f | false | 417 | 417 | return ... | +| Base::Base_f | false | 419 | 419 | { ... } | +| Base::Base_f | false | 424 | 424 | call to f | +| Base::Base_f | false | 426 | 426 | this | +| Base::Base_f | false | 427 | 427 | initializer for i | +| Base::Base_f | true | 412 | 427 | | +| Base::Base_f | true | 416 | 407 | | +| Base::Base_f | true | 417 | 416 | | +| Base::Base_f | true | 419 | 412 | | +| Base::Base_f | true | 426 | 424 | | +| Base::Base_f | true | 427 | 426 | | +| Base::Base_g | false | 371 | 371 | Base_g | +| Base::Base_g | false | 376 | 376 | declaration | +| Base::Base_g | false | 380 | 380 | 4 | +| Base::Base_g | false | 381 | 381 | return ... | +| Base::Base_g | false | 383 | 383 | { ... } | +| Base::Base_g | false | 388 | 388 | call to g | +| Base::Base_g | false | 391 | 391 | this | +| Base::Base_g | false | 392 | 392 | initializer for i | +| Base::Base_g | true | 376 | 392 | | +| Base::Base_g | true | 380 | 371 | | +| Base::Base_g | true | 381 | 380 | | +| Base::Base_g | true | 383 | 376 | | +| Base::Base_g | true | 388 | 381 | | +| Base::Base_g | true | 391 | 388 | | +| Base::Base_g | true | 392 | 391 | | +| Base::f | false | 301 | 301 | f | +| Base::f | false | 437 | 437 | call to abort | +| Base::f | false | 439 | 439 | ExprStmt | +| Base::f | false | 441 | 441 | return ... | +| Base::f | false | 443 | 443 | { ... } | +| Base::f | true | 439 | 437 | | +| Base::f | true | 441 | 301 | | +| Base::f | true | 443 | 439 | | +| Base::g | false | 230 | 230 | g | +| Base::g | false | 402 | 402 | 3 | +| Base::g | false | 403 | 403 | return ... | +| Base::g | false | 405 | 405 | { ... } | +| Base::g | true | 402 | 230 | | +| Base::g | true | 403 | 402 | | +| Base::g | true | 405 | 403 | | +| Base::operator= | false | 349 | 349 | operator= | +| Base::operator= | false | 359 | 359 | operator= | +| __va_list_tag::operator= | false | 92 | 92 | operator= | +| __va_list_tag::operator= | false | 99 | 99 | operator= | +| abort | false | 345 | 345 | abort | +| fun_f1 | false | 312 | 312 | fun_f1 | +| fun_f1 | false | 317 | 317 | declaration | +| fun_f1 | false | 319 | 319 | declaration | +| fun_f1 | false | 323 | 323 | 2 | +| fun_f1 | false | 324 | 324 | return ... | +| fun_f1 | false | 326 | 326 | { ... } | +| fun_f1 | false | 329 | 329 | call to Base | +| fun_f1 | false | 330 | 330 | new | +| fun_f1 | false | 332 | 332 | initializer for p1 | +| fun_f1 | false | 337 | 337 | call to f | +| fun_f1 | false | 339 | 339 | p1 | +| fun_f1 | false | 341 | 341 | initializer for i | +| fun_f1 | true | 317 | 332 | | +| fun_f1 | true | 319 | 341 | | +| fun_f1 | true | 323 | 312 | | +| fun_f1 | true | 324 | 323 | | +| fun_f1 | true | 326 | 317 | | +| fun_f1 | true | 329 | 330 | | +| fun_f1 | true | 330 | 319 | | +| fun_f1 | true | 332 | 329 | | +| fun_f1 | true | 337 | 324 | | +| fun_f1 | true | 339 | 337 | | +| fun_f1 | true | 341 | 339 | | +| fun_f2 | false | 276 | 276 | fun_f2 | +| fun_f2 | false | 281 | 281 | declaration | +| fun_f2 | false | 283 | 283 | declaration | +| fun_f2 | false | 287 | 287 | 2 | +| fun_f2 | false | 288 | 288 | return ... | +| fun_f2 | false | 290 | 290 | { ... } | +| fun_f2 | false | 293 | 293 | call to Base | +| fun_f2 | false | 294 | 294 | new | +| fun_f2 | false | 296 | 296 | initializer for p1 | +| fun_f2 | false | 304 | 304 | call to f | +| fun_f2 | false | 306 | 306 | p1 | +| fun_f2 | false | 308 | 308 | initializer for i | +| fun_f2 | true | 281 | 296 | | +| fun_f2 | true | 283 | 308 | | +| fun_f2 | true | 287 | 276 | | +| fun_f2 | true | 288 | 287 | | +| fun_f2 | true | 290 | 281 | | +| fun_f2 | true | 293 | 294 | | +| fun_f2 | true | 294 | 283 | | +| fun_f2 | true | 296 | 293 | | +| fun_f2 | true | 306 | 304 | | +| fun_f2 | true | 308 | 306 | | +| fun_g1 | false | 243 | 243 | fun_g1 | +| fun_g1 | false | 248 | 248 | declaration | +| fun_g1 | false | 250 | 250 | declaration | +| fun_g1 | false | 254 | 254 | 2 | +| fun_g1 | false | 255 | 255 | return ... | +| fun_g1 | false | 257 | 257 | { ... } | +| fun_g1 | false | 260 | 260 | call to Base | +| fun_g1 | false | 261 | 261 | new | +| fun_g1 | false | 263 | 263 | initializer for p1 | +| fun_g1 | false | 268 | 268 | call to g | +| fun_g1 | false | 270 | 270 | p1 | +| fun_g1 | false | 272 | 272 | initializer for i | +| fun_g1 | true | 248 | 263 | | +| fun_g1 | true | 250 | 272 | | +| fun_g1 | true | 254 | 243 | | +| fun_g1 | true | 255 | 254 | | +| fun_g1 | true | 257 | 248 | | +| fun_g1 | true | 260 | 261 | | +| fun_g1 | true | 261 | 250 | | +| fun_g1 | true | 263 | 260 | | +| fun_g1 | true | 268 | 255 | | +| fun_g1 | true | 270 | 268 | | +| fun_g1 | true | 272 | 270 | | +| fun_g2 | false | 192 | 192 | fun_g2 | +| fun_g2 | false | 197 | 197 | declaration | +| fun_g2 | false | 199 | 199 | declaration | +| fun_g2 | false | 203 | 203 | 2 | +| fun_g2 | false | 204 | 204 | return ... | +| fun_g2 | false | 206 | 206 | { ... } | +| fun_g2 | false | 214 | 214 | call to Base | +| fun_g2 | false | 223 | 223 | new | +| fun_g2 | false | 225 | 225 | initializer for p1 | +| fun_g2 | false | 235 | 235 | call to g | +| fun_g2 | false | 237 | 237 | p1 | +| fun_g2 | false | 239 | 239 | initializer for i | +| fun_g2 | true | 197 | 225 | | +| fun_g2 | true | 199 | 239 | | +| fun_g2 | true | 203 | 192 | | +| fun_g2 | true | 204 | 203 | | +| fun_g2 | true | 206 | 197 | | +| fun_g2 | true | 214 | 223 | | +| fun_g2 | true | 223 | 199 | | +| fun_g2 | true | 225 | 214 | | +| fun_g2 | true | 235 | 204 | | +| fun_g2 | true | 237 | 235 | | +| fun_g2 | true | 239 | 237 | | +| operator delete | false | 212 | 212 | operator delete | +| operator new | false | 210 | 210 | operator new | diff --git a/cpp/ql/test/query-tests/Best Practices/Magic Constants/MagicConstantsNumbers/MagicConstantsNumbers.expected b/cpp/ql/test/query-tests/Best Practices/Magic Constants/MagicConstantsNumbers/MagicConstantsNumbers.expected index 98813c7be00c..41c8a7e79d81 100644 --- a/cpp/ql/test/query-tests/Best Practices/Magic Constants/MagicConstantsNumbers/MagicConstantsNumbers.expected +++ b/cpp/ql/test/query-tests/Best Practices/Magic Constants/MagicConstantsNumbers/MagicConstantsNumbers.expected @@ -13,6 +13,12 @@ | constants.h:153:10:153:12 | 129 | Magic constant: literal '129' is repeated 31 times and should be encapsulated in a constant. | | constants.h:153:10:153:12 | 129 | Magic constant: literal '129' is repeated 31 times and should be encapsulated in a constant. | | functions.h:3:2:3:4 | 102 | Magic constant: literal '102' is repeated 21 times and should be encapsulated in a constant. | +| functions.h:3:2:3:4 | 102 | Magic constant: literal '102' is repeated 21 times and should be encapsulated in a constant. | +| functions.h:3:8:3:10 | 102 | Magic constant: literal '102' is repeated 21 times and should be encapsulated in a constant. | | functions.h:3:8:3:10 | 102 | Magic constant: literal '102' is repeated 21 times and should be encapsulated in a constant. | | functions.h:3:14:3:16 | 102 | Magic constant: literal '102' is repeated 21 times and should be encapsulated in a constant. | +| functions.h:3:14:3:16 | 102 | Magic constant: literal '102' is repeated 21 times and should be encapsulated in a constant. | | functions.h:12:11:12:13 | 103 | Magic constant: literal '103' is repeated 21 times and should be encapsulated in a constant. | +| templates.cpp:4:5:4:6 | 23 | Magic constant: literal '23' is repeated 21 times and should be encapsulated in a constant. | +| templates.cpp:4:5:4:6 | 23 | Magic constant: literal '23' is repeated 21 times and should be encapsulated in a constant. | +| templates.cpp:13:5:13:6 | 25 | Magic constant: literal '25' is repeated 21 times and should be encapsulated in a constant. | diff --git a/cpp/ql/test/query-tests/Best Practices/Magic Constants/MagicConstantsNumbers/templates.cpp b/cpp/ql/test/query-tests/Best Practices/Magic Constants/MagicConstantsNumbers/templates.cpp new file mode 100644 index 000000000000..be73c87951c1 --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Magic Constants/MagicConstantsNumbers/templates.cpp @@ -0,0 +1,18 @@ + +template +void f(T x) { + 23; + 23; 23; 23; 23; 23; 23; 23; 23; 23; 23; 23; 23; 23; 23; 23; 23; 23; 23; 23; 23; + 'A'; + 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; 'A'; +} + +void g(void) { + int i; + f(i); + 25; + 25; 25; 25; 25; 25; 25; 25; 25; 25; 25; 25; 25; 25; 25; 25; 25; 25; 25; 25; 25; + 'B'; + 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; 'B'; +} + diff --git a/cpp/ql/test/query-tests/Likely Bugs/ReturnConstTypeMember/ReturnConstTypeMember.expected b/cpp/ql/test/query-tests/Likely Bugs/ReturnConstTypeMember/ReturnConstTypeMember.expected index 83bf42666754..02c5f0868f4b 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/ReturnConstTypeMember/ReturnConstTypeMember.expected +++ b/cpp/ql/test/query-tests/Likely Bugs/ReturnConstTypeMember/ReturnConstTypeMember.expected @@ -1,4 +1,4 @@ -| templates.cpp:13:7:13:9 | fun | The 'const' modifier has no effect on return types. The 'const' modifying the return type can be removed. | +| templates.cpp:13:7:13:7 | fun | The 'const' modifier has no effect on return types. The 'const' modifying the return type can be removed. | | test.cpp:5:12:5:23 | getAConstInt | The 'const' modifier has no effect on return types. For a const function, the 'const' should go after the parameter list. | | test.cpp:11:12:11:28 | getAConstIntConst | The 'const' modifier has no effect on return types. The 'const' modifying the return type can be removed. | | test.cpp:19:19:19:36 | getAStaticConstInt | The 'const' modifier has no effect on return types. The 'const' modifying the return type can be removed. | diff --git a/cpp/ql/test/query-tests/Metrics/Dependencies/dependencies.expected b/cpp/ql/test/query-tests/Metrics/Dependencies/dependencies.expected index 8c5deabdc0e3..6c93362a0961 100644 --- a/cpp/ql/test/query-tests/Metrics/Dependencies/dependencies.expected +++ b/cpp/ql/test/query-tests/Metrics/Dependencies/dependencies.expected @@ -1,11 +1,11 @@ | file://:0:0:0:0 | declaration of 1st parameter | LibB/libb_internal.h:5:8:5:12 | thing | | file://:0:0:0:0 | declaration of 1st parameter | LibB/libb_internal.h:5:8:5:12 | thing | | include.h:3:25:3:33 | num | LibD/libd.h:5:12:5:14 | num | -| main.cpp:8:31:8:31 | call to container | LibC/libc.h:9:3:9:11 | container | +| main.cpp:8:31:8:31 | call to container | LibC/libc.h:9:3:9:3 | container | | main.cpp:8:31:8:31 | definition of x | LibB/libb_internal.h:5:8:5:12 | thing | | main.cpp:8:31:8:31 | definition of x | LibC/libc.h:5:18:5:18 | T | | main.cpp:8:31:8:31 | definition of x | LibC/libc.h:6:8:6:16 | container | | main.cpp:8:31:8:31 | definition of x | LibC/libc.h:6:8:6:16 | container | | main.cpp:10:2:10:10 | call to fun | LibA/liba.h:5:7:5:9 | fun | | main.cpp:12:9:12:19 | include_num | include.h:3:11:3:21 | include_num | -| main.cpp:13:1:13:1 | call to ~container | LibC/libc.h:10:3:10:12 | ~container | +| main.cpp:13:1:13:1 | call to ~container | LibC/libc.h:10:3:10:3 | ~container | diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 82/AV Rule 82.expected b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 82/AV Rule 82.expected index 0981be469c3e..15e166499d48 100644 --- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 82/AV Rule 82.expected +++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 82/AV Rule 82.expected @@ -1,4 +1,4 @@ | AV Rule 82.cpp:18:9:18:17 | operator= | Assignment operator in class Bad1 does not return a reference to *this. | | AV Rule 82.cpp:24:8:24:16 | operator= | Assignment operator in class Bad2 should have return type Bad2&. Otherwise a copy is created at each call. | +| AV Rule 82.cpp:63:29:63:29 | operator= | Assignment operator in class TemplateReturnAssignment does not return a reference to *this. | | AV Rule 82.cpp:63:29:63:37 | operator= | Assignment operator in class TemplateReturnAssignment does not return a reference to *this. | -| AV Rule 82.cpp:63:29:63:37 | operator= | Assignment operator in class TemplateReturnAssignment does not return a reference to *this. | diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 85/AV Rule 85.cpp b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 85/AV Rule 85.cpp index e745e5e904fe..292b8857cb97 100644 --- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 85/AV Rule 85.cpp +++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 85/AV Rule 85.cpp @@ -124,3 +124,25 @@ void f9(void) { b = myClass9 >= myClass9; } +template +class MyClass10 { +public: + int i; + template + bool operator< (const MyClass10 &rhs){ return i < rhs.i; } + template + bool operator>= (const MyClass10 &rhs){ return !(*this < rhs); } + // GOOD + template + bool operator> (const MyClass10 &rhs){ return i < rhs.i; } + template + bool operator<= (const MyClass10 &rhs){ return i >= rhs.i; } + // BAD: neither operator defined in terms of the other +}; +void f10(void) { + bool b; + MyClass10 myClass10; + b = myClass10 < myClass10; + b = myClass10 > myClass10; +} + diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 85/AV Rule 85.expected b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 85/AV Rule 85.expected index 379cab5c1d60..63e1b23f4024 100644 --- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 85/AV Rule 85.expected +++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 85/AV Rule 85.expected @@ -3,43 +3,7 @@ | AV Rule 85.cpp:9:7:9:14 | MyClass2 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator>= is declared on line 13, but it is not defined in terms of its opposite operator operator<. | | AV Rule 85.cpp:25:7:25:14 | MyClass4 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator<= is declared on line 32, but it is not defined in terms of its opposite operator operator>. | | AV Rule 85.cpp:25:7:25:14 | MyClass4 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator> is declared on line 31, but it is not defined in terms of its opposite operator operator<=. | -| AV Rule 85.cpp:37:7:37:14 | MyClass5 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator< is declared on line 40, but it is not defined in terms of its opposite operator operator>=. | -| AV Rule 85.cpp:37:7:37:14 | MyClass5 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator<= is declared on line 44, but it is not defined in terms of its opposite operator operator>. | -| AV Rule 85.cpp:37:7:37:14 | MyClass5 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator> is declared on line 43, but it is not defined in terms of its opposite operator operator<=. | -| AV Rule 85.cpp:37:7:37:14 | MyClass5 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator>= is declared on line 41, but it is not defined in terms of its opposite operator operator<. | -| AV Rule 85.cpp:49:7:49:14 | MyClass6 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator< is declared on line 52, but it is not defined in terms of its opposite operator operator>=. | -| AV Rule 85.cpp:49:7:49:14 | MyClass6 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator<= is declared on line 56, but it is not defined in terms of its opposite operator operator>. | -| AV Rule 85.cpp:49:7:49:14 | MyClass6 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator> is declared on line 55, but it is not defined in terms of its opposite operator operator<=. | -| AV Rule 85.cpp:49:7:49:14 | MyClass6 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator>= is declared on line 53, but it is not defined in terms of its opposite operator operator<. | -| AV Rule 85.cpp:49:7:49:14 | MyClass6 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator< is declared on line 52, but it is not defined in terms of its opposite operator operator>=. | -| AV Rule 85.cpp:49:7:49:14 | MyClass6 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator<= is declared on line 56, but it is not defined in terms of its opposite operator operator>. | -| AV Rule 85.cpp:49:7:49:14 | MyClass6 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator> is declared on line 55, but it is not defined in terms of its opposite operator operator<=. | -| AV Rule 85.cpp:49:7:49:14 | MyClass6 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator>= is declared on line 53, but it is not defined in terms of its opposite operator operator<. | -| AV Rule 85.cpp:62:7:62:14 | MyClass7 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator< is declared on line 66, but it is not defined in terms of its opposite operator operator>=. | -| AV Rule 85.cpp:62:7:62:14 | MyClass7 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator<= is declared on line 73, but it is not defined in terms of its opposite operator operator>. | -| AV Rule 85.cpp:62:7:62:14 | MyClass7 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator> is declared on line 71, but it is not defined in terms of its opposite operator operator<=. | -| AV Rule 85.cpp:62:7:62:14 | MyClass7 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator>= is declared on line 68, but it is not defined in terms of its opposite operator operator<. | -| AV Rule 85.cpp:62:7:62:14 | MyClass7 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator< is declared on line 66, but it is not defined in terms of its opposite operator operator>=. | -| AV Rule 85.cpp:62:7:62:14 | MyClass7 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator<= is declared on line 73, but it is not defined in terms of its opposite operator operator>. | -| AV Rule 85.cpp:62:7:62:14 | MyClass7 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator> is declared on line 71, but it is not defined in terms of its opposite operator operator<=. | -| AV Rule 85.cpp:62:7:62:14 | MyClass7 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator>= is declared on line 68, but it is not defined in terms of its opposite operator operator<. | -| AV Rule 85.cpp:79:7:79:14 | MyClass8 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator< is declared on line 83, but it is not defined in terms of its opposite operator operator>=. | | AV Rule 85.cpp:79:7:79:14 | MyClass8 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator<= is declared on line 90, but it is not defined in terms of its opposite operator operator>. | | AV Rule 85.cpp:79:7:79:14 | MyClass8 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator> is declared on line 88, but it is not defined in terms of its opposite operator operator<=. | -| AV Rule 85.cpp:79:7:79:14 | MyClass8 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator>= is declared on line 85, but it is not defined in terms of its opposite operator operator<. | -| AV Rule 85.cpp:79:7:79:14 | MyClass8 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator< is declared on line 83, but it is not defined in terms of its opposite operator operator>=. | -| AV Rule 85.cpp:79:7:79:14 | MyClass8 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator<= is declared on line 90, but it is not defined in terms of its opposite operator operator>. | -| AV Rule 85.cpp:79:7:79:14 | MyClass8 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator> is declared on line 88, but it is not defined in terms of its opposite operator operator<=. | -| AV Rule 85.cpp:79:7:79:14 | MyClass8 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator>= is declared on line 85, but it is not defined in terms of its opposite operator operator<. | -| AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator< is declared on line 107, but it is not defined in terms of its opposite operator operator>=. | | AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator<= is declared on line 114, but it is not defined in terms of its opposite operator operator>. | | AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator> is declared on line 112, but it is not defined in terms of its opposite operator operator<=. | -| AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator>= is declared on line 109, but it is not defined in terms of its opposite operator operator<. | -| AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator< is declared on line 107, but it is not defined in terms of its opposite operator operator>=. | -| AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator<= is declared on line 114, but it is not defined in terms of its opposite operator operator>. | -| AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator> is declared on line 112, but it is not defined in terms of its opposite operator operator<=. | -| AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator>= is declared on line 109, but it is not defined in terms of its opposite operator operator<. | -| AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator< is declared on line 107, but it is not defined in terms of its opposite operator operator>=. | -| AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator<= is declared on line 114, but it is not defined in terms of its opposite operator operator>. | -| AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator> is declared on line 112, but it is not defined in terms of its opposite operator operator<=. | -| AV Rule 85.cpp:103:7:103:14 | MyClass9 | When two operators are opposites, both should be defined and one should be defined in terms of the other. Operator operator>= is declared on line 109, but it is not defined in terms of its opposite operator operator<. | diff --git a/cpp/ql/test/successor-tests/conditional_destructors/cfg.expected b/cpp/ql/test/successor-tests/conditional_destructors/cfg.expected index 9b11047f62f7..7a28d79a4a34 100644 --- a/cpp/ql/test/successor-tests/conditional_destructors/cfg.expected +++ b/cpp/ql/test/successor-tests/conditional_destructors/cfg.expected @@ -1,167 +1,167 @@ -| C1::C1 | false | 131 | 131 | C1 | -| C1::C1 | false | 136 | 136 | this | -| C1::C1 | false | 137 | 137 | val | -| C1::C1 | false | 139 | 139 | x | -| C1::C1 | false | 141 | 141 | ... = ... | -| C1::C1 | false | 143 | 143 | ExprStmt | -| C1::C1 | false | 145 | 145 | return ... | -| C1::C1 | false | 147 | 147 | { ... } | -| C1::C1 | false | 178 | 178 | C1 | -| C1::C1 | false | 180 | 180 | C1 | -| C1::C1 | true | 136 | 137 | | -| C1::C1 | true | 137 | 141 | | -| C1::C1 | true | 139 | 136 | | -| C1::C1 | true | 141 | 145 | | -| C1::C1 | true | 143 | 139 | | -| C1::C1 | true | 145 | 131 | | -| C1::C1 | true | 147 | 143 | | -| C1::operator= | false | 174 | 174 | operator= | -| C1::operator= | false | 176 | 176 | operator= | -| C1::operator== | false | 153 | 153 | operator== | -| C1::operator== | false | 158 | 158 | this | -| C1::operator== | false | 159 | 159 | val | -| C1::operator== | false | 161 | 161 | other | -| C1::operator== | false | 163 | 163 | (reference dereference) | -| C1::operator== | false | 165 | 165 | val | -| C1::operator== | false | 167 | 167 | ... == ... | -| C1::operator== | false | 169 | 169 | return ... | -| C1::operator== | false | 171 | 171 | { ... } | -| C1::operator== | true | 158 | 159 | | -| C1::operator== | true | 159 | 161 | | -| C1::operator== | true | 161 | 165 | | -| C1::operator== | true | 165 | 167 | | -| C1::operator== | true | 167 | 153 | | -| C1::operator== | true | 169 | 158 | | -| C1::operator== | true | 171 | 169 | | -| C2::C2 | false | 195 | 195 | C2 | -| C2::C2 | false | 200 | 200 | this | -| C2::C2 | false | 201 | 201 | val | -| C2::C2 | false | 203 | 203 | x | -| C2::C2 | false | 205 | 205 | ... = ... | -| C2::C2 | false | 207 | 207 | ExprStmt | -| C2::C2 | false | 209 | 209 | return ... | -| C2::C2 | false | 211 | 211 | { ... } | -| C2::C2 | false | 250 | 250 | C2 | -| C2::C2 | true | 200 | 201 | | -| C2::C2 | true | 201 | 205 | | -| C2::C2 | true | 203 | 200 | | -| C2::C2 | true | 205 | 209 | | -| C2::C2 | true | 207 | 203 | | -| C2::C2 | true | 209 | 195 | | -| C2::C2 | true | 211 | 207 | | -| C2::operator= | false | 248 | 248 | operator= | -| C2::operator== | false | 228 | 228 | operator== | -| C2::operator== | false | 233 | 233 | this | -| C2::operator== | false | 234 | 234 | val | -| C2::operator== | false | 236 | 236 | other | -| C2::operator== | false | 238 | 238 | (reference dereference) | -| C2::operator== | false | 240 | 240 | val | -| C2::operator== | false | 242 | 242 | ... == ... | -| C2::operator== | false | 244 | 244 | return ... | -| C2::operator== | false | 246 | 246 | { ... } | -| C2::operator== | true | 233 | 234 | | -| C2::operator== | true | 234 | 236 | | -| C2::operator== | true | 236 | 240 | | -| C2::operator== | true | 240 | 242 | | -| C2::operator== | true | 242 | 228 | | -| C2::operator== | true | 244 | 233 | | -| C2::operator== | true | 246 | 244 | | -| C2::~C2 | false | 214 | 214 | ~C2 | -| C2::~C2 | false | 218 | 218 | ; | -| C2::~C2 | false | 220 | 220 | return ... | -| C2::~C2 | false | 222 | 222 | { ... } | -| C2::~C2 | true | 218 | 220 | | -| C2::~C2 | true | 220 | 214 | | -| C2::~C2 | true | 222 | 218 | | -| __va_list_tag::operator= | false | 61 | 61 | operator= | -| __va_list_tag::operator= | false | 65 | 65 | operator= | -| f1 | false | 260 | 260 | f1 | -| f1 | false | 268 | 268 | call to operator== | +| C1::C1 | false | 264 | 264 | C1 | +| C1::C1 | false | 386 | 386 | C1 | +| C1::C1 | false | 390 | 390 | C1 | +| C1::C1 | false | 427 | 427 | this | +| C1::C1 | false | 428 | 428 | val | +| C1::C1 | false | 430 | 430 | x | +| C1::C1 | false | 432 | 432 | ... = ... | +| C1::C1 | false | 434 | 434 | ExprStmt | +| C1::C1 | false | 436 | 436 | return ... | +| C1::C1 | false | 438 | 438 | { ... } | +| C1::C1 | true | 427 | 428 | | +| C1::C1 | true | 428 | 432 | | +| C1::C1 | true | 430 | 427 | | +| C1::C1 | true | 432 | 436 | | +| C1::C1 | true | 434 | 430 | | +| C1::C1 | true | 436 | 264 | | +| C1::C1 | true | 438 | 434 | | +| C1::operator= | false | 376 | 376 | operator= | +| C1::operator= | false | 382 | 382 | operator= | +| C1::operator== | false | 254 | 254 | operator== | +| C1::operator== | false | 404 | 404 | this | +| C1::operator== | false | 406 | 406 | val | +| C1::operator== | false | 408 | 408 | other | +| C1::operator== | false | 410 | 410 | (reference dereference) | +| C1::operator== | false | 411 | 411 | val | +| C1::operator== | false | 413 | 413 | ... == ... | +| C1::operator== | false | 415 | 415 | return ... | +| C1::operator== | false | 417 | 417 | { ... } | +| C1::operator== | true | 404 | 406 | | +| C1::operator== | true | 406 | 408 | | +| C1::operator== | true | 408 | 411 | | +| C1::operator== | true | 411 | 413 | | +| C1::operator== | true | 413 | 254 | | +| C1::operator== | true | 415 | 404 | | +| C1::operator== | true | 417 | 415 | | +| C2::C2 | false | 201 | 201 | C2 | +| C2::C2 | false | 313 | 313 | C2 | +| C2::C2 | false | 361 | 361 | this | +| C2::C2 | false | 362 | 362 | val | +| C2::C2 | false | 364 | 364 | x | +| C2::C2 | false | 366 | 366 | ... = ... | +| C2::C2 | false | 368 | 368 | ExprStmt | +| C2::C2 | false | 370 | 370 | return ... | +| C2::C2 | false | 372 | 372 | { ... } | +| C2::C2 | true | 361 | 362 | | +| C2::C2 | true | 362 | 366 | | +| C2::C2 | true | 364 | 361 | | +| C2::C2 | true | 366 | 370 | | +| C2::C2 | true | 368 | 364 | | +| C2::C2 | true | 370 | 201 | | +| C2::C2 | true | 372 | 368 | | +| C2::operator= | false | 307 | 307 | operator= | +| C2::operator== | false | 191 | 191 | operator== | +| C2::operator== | false | 327 | 327 | this | +| C2::operator== | false | 329 | 329 | val | +| C2::operator== | false | 331 | 331 | other | +| C2::operator== | false | 333 | 333 | (reference dereference) | +| C2::operator== | false | 334 | 334 | val | +| C2::operator== | false | 336 | 336 | ... == ... | +| C2::operator== | false | 338 | 338 | return ... | +| C2::operator== | false | 340 | 340 | { ... } | +| C2::operator== | true | 327 | 329 | | +| C2::operator== | true | 329 | 331 | | +| C2::operator== | true | 331 | 334 | | +| C2::operator== | true | 334 | 336 | | +| C2::operator== | true | 336 | 191 | | +| C2::operator== | true | 338 | 327 | | +| C2::operator== | true | 340 | 338 | | +| C2::~C2 | false | 342 | 342 | ~C2 | +| C2::~C2 | false | 347 | 347 | ; | +| C2::~C2 | false | 349 | 349 | return ... | +| C2::~C2 | false | 351 | 351 | { ... } | +| C2::~C2 | true | 347 | 349 | | +| C2::~C2 | true | 349 | 342 | | +| C2::~C2 | true | 351 | 347 | | +| __va_list_tag::operator= | false | 90 | 90 | operator= | +| __va_list_tag::operator= | false | 97 | 97 | operator= | +| f1 | false | 244 | 244 | f1 | +| f1 | false | 261 | 261 | call to operator== | +| f1 | false | 262 | 262 | call to C1 | +| f1 | false | 267 | 267 | 1 | +| f1 | false | 268 | 268 | (const C1)... | | f1 | false | 269 | 269 | call to C1 | -| f1 | false | 273 | 273 | 1 | +| f1 | false | 273 | 273 | 2 | | f1 | false | 274 | 274 | (const C1)... | -| f1 | false | 276 | 276 | call to C1 | -| f1 | false | 280 | 280 | 2 | -| f1 | false | 281 | 281 | (const C1)... | -| f1 | false | 283 | 283 | (reference to) | -| f1 | false | 285 | 285 | ; | -| f1 | false | 287 | 287 | { ... } | -| f1 | false | 289 | 289 | if (...) ... | -| f1 | false | 292 | 292 | call to operator== | -| f1 | false | 293 | 293 | call to C1 | -| f1 | false | 297 | 297 | 3 | -| f1 | false | 298 | 298 | (const C1)... | -| f1 | false | 300 | 300 | call to C1 | -| f1 | false | 304 | 304 | 3 | -| f1 | false | 305 | 305 | (const C1)... | -| f1 | false | 307 | 307 | (reference to) | -| f1 | false | 309 | 309 | ; | -| f1 | false | 311 | 311 | { ... } | -| f1 | false | 313 | 313 | if (...) ... | -| f1 | false | 315 | 315 | return ... | -| f1 | false | 317 | 317 | { ... } | -| f1 | true | 268 | 287 | T | -| f1 | true | 268 | 313 | F | -| f1 | true | 269 | 268 | | +| f1 | false | 275 | 275 | (reference to) | +| f1 | false | 276 | 276 | ; | +| f1 | false | 278 | 278 | { ... } | +| f1 | false | 280 | 280 | if (...) ... | +| f1 | false | 283 | 283 | call to operator== | +| f1 | false | 284 | 284 | call to C1 | +| f1 | false | 288 | 288 | 3 | +| f1 | false | 289 | 289 | (const C1)... | +| f1 | false | 290 | 290 | call to C1 | +| f1 | false | 294 | 294 | 3 | +| f1 | false | 295 | 295 | (const C1)... | +| f1 | false | 296 | 296 | (reference to) | +| f1 | false | 297 | 297 | ; | +| f1 | false | 299 | 299 | { ... } | +| f1 | false | 301 | 301 | if (...) ... | +| f1 | false | 303 | 303 | return ... | +| f1 | false | 305 | 305 | { ... } | +| f1 | true | 261 | 278 | T | +| f1 | true | 261 | 301 | F | +| f1 | true | 262 | 261 | | +| f1 | true | 267 | 262 | | +| f1 | true | 269 | 267 | | | f1 | true | 273 | 269 | | -| f1 | true | 276 | 273 | | -| f1 | true | 280 | 276 | | -| f1 | true | 285 | 313 | | -| f1 | true | 287 | 285 | | -| f1 | true | 289 | 280 | | -| f1 | true | 292 | 311 | T | -| f1 | true | 292 | 315 | F | -| f1 | true | 293 | 292 | | -| f1 | true | 297 | 293 | | -| f1 | true | 300 | 297 | | -| f1 | true | 304 | 300 | | -| f1 | true | 309 | 315 | | -| f1 | true | 311 | 309 | | -| f1 | true | 313 | 304 | | -| f1 | true | 315 | 260 | | -| f1 | true | 317 | 289 | | -| f2 | false | 320 | 320 | f2 | -| f2 | false | 328 | 328 | call to operator== | -| f2 | false | 329 | 329 | call to C2 | -| f2 | false | 333 | 333 | 1 | -| f2 | false | 334 | 334 | (const C2)... | -| f2 | false | 336 | 336 | call to C2 | -| f2 | false | 340 | 340 | 2 | -| f2 | false | 341 | 341 | (const C2)... | -| f2 | false | 343 | 343 | (reference to) | -| f2 | false | 345 | 345 | ; | -| f2 | false | 347 | 347 | { ... } | -| f2 | false | 349 | 349 | if (...) ... | -| f2 | false | 352 | 352 | call to operator== | -| f2 | false | 353 | 353 | call to C2 | -| f2 | false | 357 | 357 | 3 | -| f2 | false | 358 | 358 | (const C2)... | -| f2 | false | 360 | 360 | call to C2 | -| f2 | false | 364 | 364 | 3 | -| f2 | false | 365 | 365 | (const C2)... | -| f2 | false | 367 | 367 | (reference to) | -| f2 | false | 369 | 369 | ; | -| f2 | false | 371 | 371 | { ... } | -| f2 | false | 373 | 373 | if (...) ... | -| f2 | false | 375 | 375 | return ... | -| f2 | false | 377 | 377 | { ... } | -| f2 | true | 328 | 347 | T | -| f2 | true | 328 | 373 | F | -| f2 | true | 329 | 328 | | -| f2 | true | 333 | 329 | | -| f2 | true | 336 | 333 | | -| f2 | true | 340 | 336 | | -| f2 | true | 345 | 373 | | -| f2 | true | 347 | 345 | | -| f2 | true | 349 | 340 | | -| f2 | true | 352 | 371 | T | -| f2 | true | 352 | 375 | F | -| f2 | true | 353 | 352 | | -| f2 | true | 357 | 353 | | -| f2 | true | 360 | 357 | | -| f2 | true | 364 | 360 | | -| f2 | true | 369 | 375 | | -| f2 | true | 371 | 369 | | -| f2 | true | 373 | 364 | | -| f2 | true | 375 | 320 | | -| f2 | true | 377 | 349 | | +| f1 | true | 276 | 301 | | +| f1 | true | 278 | 276 | | +| f1 | true | 280 | 273 | | +| f1 | true | 283 | 299 | T | +| f1 | true | 283 | 303 | F | +| f1 | true | 284 | 283 | | +| f1 | true | 288 | 284 | | +| f1 | true | 290 | 288 | | +| f1 | true | 294 | 290 | | +| f1 | true | 297 | 303 | | +| f1 | true | 299 | 297 | | +| f1 | true | 301 | 294 | | +| f1 | true | 303 | 244 | | +| f1 | true | 305 | 280 | | +| f2 | false | 181 | 181 | f2 | +| f2 | false | 198 | 198 | call to operator== | +| f2 | false | 199 | 199 | call to C2 | +| f2 | false | 204 | 204 | 1 | +| f2 | false | 205 | 205 | (const C2)... | +| f2 | false | 206 | 206 | call to C2 | +| f2 | false | 210 | 210 | 2 | +| f2 | false | 211 | 211 | (const C2)... | +| f2 | false | 212 | 212 | (reference to) | +| f2 | false | 213 | 213 | ; | +| f2 | false | 215 | 215 | { ... } | +| f2 | false | 217 | 217 | if (...) ... | +| f2 | false | 220 | 220 | call to operator== | +| f2 | false | 221 | 221 | call to C2 | +| f2 | false | 225 | 225 | 3 | +| f2 | false | 226 | 226 | (const C2)... | +| f2 | false | 227 | 227 | call to C2 | +| f2 | false | 231 | 231 | 3 | +| f2 | false | 232 | 232 | (const C2)... | +| f2 | false | 233 | 233 | (reference to) | +| f2 | false | 234 | 234 | ; | +| f2 | false | 236 | 236 | { ... } | +| f2 | false | 238 | 238 | if (...) ... | +| f2 | false | 240 | 240 | return ... | +| f2 | false | 242 | 242 | { ... } | +| f2 | true | 198 | 215 | T | +| f2 | true | 198 | 238 | F | +| f2 | true | 199 | 198 | | +| f2 | true | 204 | 199 | | +| f2 | true | 206 | 204 | | +| f2 | true | 210 | 206 | | +| f2 | true | 213 | 238 | | +| f2 | true | 215 | 213 | | +| f2 | true | 217 | 210 | | +| f2 | true | 220 | 236 | T | +| f2 | true | 220 | 240 | F | +| f2 | true | 221 | 220 | | +| f2 | true | 225 | 221 | | +| f2 | true | 227 | 225 | | +| f2 | true | 231 | 227 | | +| f2 | true | 234 | 240 | | +| f2 | true | 236 | 234 | | +| f2 | true | 238 | 231 | | +| f2 | true | 240 | 181 | | +| f2 | true | 242 | 217 | | diff --git a/cpp/ql/test/successor-tests/exceptionhandler/ellipsisexceptionhandler/graphable.expected b/cpp/ql/test/successor-tests/exceptionhandler/ellipsisexceptionhandler/graphable.expected index 76d6a4d32607..a28b10378077 100644 --- a/cpp/ql/test/successor-tests/exceptionhandler/ellipsisexceptionhandler/graphable.expected +++ b/cpp/ql/test/successor-tests/exceptionhandler/ellipsisexceptionhandler/graphable.expected @@ -1,59 +1,59 @@ -| __va_list_tag::operator= | false | 61 | 61 | operator= | -| __va_list_tag::operator= | false | 65 | 65 | operator= | -| f | false | 128 | 128 | f | -| f | false | 139 | 139 | 1 | -| f | false | 140 | 140 | throw ... | -| f | false | 142 | 142 | ExprStmt | -| f | false | 144 | 144 | { ... } | -| f | false | 146 | 146 | { ... } | -| f | false | 148 | 148 | { ... } | -| f | false | 150 | 150 | | -| f | false | 151 | 151 | | -| f | false | 152 | 152 | try { ... } | -| f | false | 154 | 154 | { ... } | -| f | false | 156 | 156 | { ... } | -| f | false | 158 | 158 | | -| f | false | 159 | 159 | try { ... } | -| f | false | 161 | 161 | return ... | -| f | false | 163 | 163 | { ... } | -| f | true | 139 | 140 | | -| f | true | 140 | 150 | | -| f | true | 142 | 139 | | -| f | true | 144 | 142 | | -| f | true | 146 | 161 | | -| f | true | 148 | 161 | | -| f | true | 150 | 146 | | -| f | true | 150 | 151 | | -| f | true | 151 | 148 | | -| f | true | 152 | 144 | | -| f | true | 154 | 152 | | -| f | true | 156 | 161 | | -| f | true | 158 | 128 | | -| f | true | 158 | 156 | | -| f | true | 159 | 154 | | -| f | true | 161 | 128 | | -| f | true | 163 | 159 | | -| g | false | 167 | 167 | g | -| g | false | 171 | 171 | condition | -| g | false | 175 | 175 | 1 | -| g | false | 176 | 176 | throw ... | -| g | false | 178 | 178 | ExprStmt | -| g | false | 180 | 180 | if (...) ... | -| g | false | 182 | 182 | { ... } | -| g | false | 184 | 184 | { ... } | -| g | false | 186 | 186 | | -| g | false | 187 | 187 | try { ... } | -| g | false | 189 | 189 | return ... | -| g | false | 191 | 191 | { ... } | -| g | true | 171 | 178 | T | -| g | true | 171 | 189 | F | -| g | true | 175 | 176 | | -| g | true | 176 | 186 | | -| g | true | 178 | 175 | | -| g | true | 180 | 171 | | -| g | true | 182 | 180 | | -| g | true | 184 | 189 | | -| g | true | 186 | 184 | | -| g | true | 187 | 182 | | -| g | true | 189 | 167 | | -| g | true | 191 | 187 | | +| __va_list_tag::operator= | false | 92 | 92 | operator= | +| __va_list_tag::operator= | false | 99 | 99 | operator= | +| f | false | 175 | 175 | f | +| f | false | 182 | 182 | 1 | +| f | false | 183 | 183 | throw ... | +| f | false | 185 | 185 | ExprStmt | +| f | false | 187 | 187 | { ... } | +| f | false | 192 | 192 | { ... } | +| f | false | 194 | 194 | { ... } | +| f | false | 196 | 196 | | +| f | false | 197 | 197 | | +| f | false | 198 | 198 | try { ... } | +| f | false | 200 | 200 | { ... } | +| f | false | 205 | 205 | { ... } | +| f | false | 207 | 207 | | +| f | false | 208 | 208 | try { ... } | +| f | false | 210 | 210 | return ... | +| f | false | 212 | 212 | { ... } | +| f | true | 182 | 183 | | +| f | true | 183 | 196 | | +| f | true | 185 | 182 | | +| f | true | 187 | 185 | | +| f | true | 192 | 210 | | +| f | true | 194 | 210 | | +| f | true | 196 | 192 | | +| f | true | 196 | 197 | | +| f | true | 197 | 194 | | +| f | true | 198 | 187 | | +| f | true | 200 | 198 | | +| f | true | 205 | 210 | | +| f | true | 207 | 175 | | +| f | true | 207 | 205 | | +| f | true | 208 | 200 | | +| f | true | 210 | 175 | | +| f | true | 212 | 208 | | +| g | false | 145 | 145 | g | +| g | false | 153 | 153 | condition | +| g | false | 157 | 157 | 1 | +| g | false | 158 | 158 | throw ... | +| g | false | 160 | 160 | ExprStmt | +| g | false | 162 | 162 | if (...) ... | +| g | false | 164 | 164 | { ... } | +| g | false | 166 | 166 | { ... } | +| g | false | 168 | 168 | | +| g | false | 169 | 169 | try { ... } | +| g | false | 171 | 171 | return ... | +| g | false | 173 | 173 | { ... } | +| g | true | 153 | 160 | T | +| g | true | 153 | 171 | F | +| g | true | 157 | 158 | | +| g | true | 158 | 168 | | +| g | true | 160 | 157 | | +| g | true | 162 | 153 | | +| g | true | 164 | 162 | | +| g | true | 166 | 171 | | +| g | true | 168 | 166 | | +| g | true | 169 | 164 | | +| g | true | 171 | 145 | | +| g | true | 173 | 169 | | diff --git a/cpp/ql/test/successor-tests/exceptionhandler/exceptionhandler/graphable.expected b/cpp/ql/test/successor-tests/exceptionhandler/exceptionhandler/graphable.expected index a434cbca885c..4a664fd60a7b 100644 --- a/cpp/ql/test/successor-tests/exceptionhandler/exceptionhandler/graphable.expected +++ b/cpp/ql/test/successor-tests/exceptionhandler/exceptionhandler/graphable.expected @@ -1,66 +1,66 @@ -| C::operator= | false | 75 | 75 | operator= | -| C::operator= | false | 81 | 81 | operator= | -| D::operator= | false | 91 | 91 | operator= | -| D::operator= | false | 97 | 97 | operator= | -| __va_list_tag::operator= | false | 60 | 60 | operator= | -| __va_list_tag::operator= | false | 66 | 66 | operator= | -| f | false | 115 | 115 | f | -| f | false | 120 | 120 | call to g | -| f | false | 122 | 122 | ExprStmt | -| f | false | 126 | 126 | 2 | -| f | false | 127 | 127 | throw ... | -| f | false | 129 | 129 | ExprStmt | -| f | false | 131 | 131 | label ...: | -| f | false | 133 | 133 | { ... } | -| f | false | 141 | 141 | 4 | -| f | false | 142 | 142 | ExprStmt | -| f | false | 144 | 144 | { ... } | -| f | false | 146 | 146 | | -| f | false | 147 | 147 | try { ... } | -| f | false | 149 | 149 | { ... } | -| f | false | 157 | 157 | 5 | -| f | false | 158 | 158 | ExprStmt | -| f | false | 160 | 160 | { ... } | -| f | false | 168 | 168 | 6 | -| f | false | 169 | 169 | ExprStmt | -| f | false | 171 | 171 | { ... } | -| f | false | 173 | 173 | | -| f | false | 174 | 174 | | -| f | false | 175 | 175 | try { ... } | -| f | false | 177 | 177 | return ... | -| f | false | 179 | 179 | { ... } | -| f | true | 122 | 120 | | -| f | true | 126 | 127 | | -| f | true | 127 | 146 | | -| f | true | 129 | 126 | | -| f | true | 131 | 177 | | -| f | true | 133 | 122 | | -| f | true | 141 | 177 | | -| f | true | 142 | 141 | | -| f | true | 144 | 142 | | -| f | true | 146 | 144 | | -| f | true | 146 | 173 | | -| f | true | 147 | 133 | | -| f | true | 149 | 147 | | -| f | true | 157 | 177 | | -| f | true | 158 | 157 | | -| f | true | 160 | 158 | | -| f | true | 168 | 177 | | -| f | true | 169 | 168 | | -| f | true | 171 | 169 | | -| f | true | 173 | 160 | | -| f | true | 173 | 174 | | -| f | true | 174 | 115 | | -| f | true | 174 | 171 | | -| f | true | 175 | 149 | | -| f | true | 177 | 115 | | -| f | true | 179 | 175 | | -| g | false | 102 | 102 | g | -| g | false | 107 | 107 | 1 | -| g | false | 108 | 108 | throw ... | -| g | false | 110 | 110 | ExprStmt | -| g | false | 112 | 112 | { ... } | -| g | true | 107 | 108 | | -| g | true | 108 | 102 | | -| g | true | 110 | 107 | | -| g | true | 112 | 110 | | +| C::operator= | false | 250 | 250 | operator= | +| C::operator= | false | 261 | 261 | operator= | +| D::operator= | false | 232 | 232 | operator= | +| D::operator= | false | 243 | 243 | operator= | +| __va_list_tag::operator= | false | 92 | 92 | operator= | +| __va_list_tag::operator= | false | 99 | 99 | operator= | +| f | false | 149 | 149 | f | +| f | false | 157 | 157 | call to g | +| f | false | 159 | 159 | ExprStmt | +| f | false | 163 | 163 | 2 | +| f | false | 164 | 164 | throw ... | +| f | false | 166 | 166 | ExprStmt | +| f | false | 168 | 168 | label ...: | +| f | false | 170 | 170 | { ... } | +| f | false | 178 | 178 | 4 | +| f | false | 179 | 179 | ExprStmt | +| f | false | 181 | 181 | { ... } | +| f | false | 183 | 183 | | +| f | false | 184 | 184 | try { ... } | +| f | false | 186 | 186 | { ... } | +| f | false | 193 | 193 | 5 | +| f | false | 194 | 194 | ExprStmt | +| f | false | 196 | 196 | { ... } | +| f | false | 203 | 203 | 6 | +| f | false | 204 | 204 | ExprStmt | +| f | false | 206 | 206 | { ... } | +| f | false | 208 | 208 | | +| f | false | 209 | 209 | | +| f | false | 210 | 210 | try { ... } | +| f | false | 212 | 212 | return ... | +| f | false | 214 | 214 | { ... } | +| f | true | 159 | 157 | | +| f | true | 163 | 164 | | +| f | true | 164 | 183 | | +| f | true | 166 | 163 | | +| f | true | 168 | 212 | | +| f | true | 170 | 159 | | +| f | true | 178 | 212 | | +| f | true | 179 | 178 | | +| f | true | 181 | 179 | | +| f | true | 183 | 181 | | +| f | true | 183 | 208 | | +| f | true | 184 | 170 | | +| f | true | 186 | 184 | | +| f | true | 193 | 212 | | +| f | true | 194 | 193 | | +| f | true | 196 | 194 | | +| f | true | 203 | 212 | | +| f | true | 204 | 203 | | +| f | true | 206 | 204 | | +| f | true | 208 | 196 | | +| f | true | 208 | 209 | | +| f | true | 209 | 149 | | +| f | true | 209 | 206 | | +| f | true | 210 | 186 | | +| f | true | 212 | 149 | | +| f | true | 214 | 210 | | +| g | false | 154 | 154 | g | +| g | false | 222 | 222 | 1 | +| g | false | 223 | 223 | throw ... | +| g | false | 225 | 225 | ExprStmt | +| g | false | 227 | 227 | { ... } | +| g | true | 222 | 223 | | +| g | true | 223 | 154 | | +| g | true | 225 | 222 | | +| g | true | 227 | 225 | | diff --git a/cpp/ql/test/successor-tests/stackvariables/stackvariables/graphable.expected b/cpp/ql/test/successor-tests/stackvariables/stackvariables/graphable.expected index 57bc35805e37..03f49c4b623b 100644 --- a/cpp/ql/test/successor-tests/stackvariables/stackvariables/graphable.expected +++ b/cpp/ql/test/successor-tests/stackvariables/stackvariables/graphable.expected @@ -1,512 +1,512 @@ -| BoxedInt::BoxedInt | false | 70 | 70 | BoxedInt | -| BoxedInt::BoxedInt | false | 75 | 75 | this | -| BoxedInt::BoxedInt | false | 76 | 76 | m_ptr | -| BoxedInt::BoxedInt | false | 80 | 80 | x | -| BoxedInt::BoxedInt | false | 82 | 82 | new | -| BoxedInt::BoxedInt | false | 84 | 84 | ... = ... | -| BoxedInt::BoxedInt | false | 86 | 86 | ExprStmt | -| BoxedInt::BoxedInt | false | 88 | 88 | return ... | -| BoxedInt::BoxedInt | false | 90 | 90 | { ... } | -| BoxedInt::BoxedInt | false | 134 | 134 | BoxedInt | -| BoxedInt::BoxedInt | true | 75 | 76 | | -| BoxedInt::BoxedInt | true | 76 | 84 | | -| BoxedInt::BoxedInt | true | 80 | 82 | | -| BoxedInt::BoxedInt | true | 82 | 75 | | -| BoxedInt::BoxedInt | true | 84 | 88 | | -| BoxedInt::BoxedInt | true | 86 | 80 | | -| BoxedInt::BoxedInt | true | 88 | 70 | | -| BoxedInt::BoxedInt | true | 90 | 86 | | -| BoxedInt::operator int | false | 112 | 112 | operator int | -| BoxedInt::operator int | false | 116 | 116 | this | -| BoxedInt::operator int | false | 117 | 117 | m_ptr | -| BoxedInt::operator int | false | 119 | 119 | * ... | -| BoxedInt::operator int | false | 121 | 121 | return ... | -| BoxedInt::operator int | false | 123 | 123 | { ... } | -| BoxedInt::operator int | true | 116 | 117 | | -| BoxedInt::operator int | true | 117 | 119 | | -| BoxedInt::operator int | true | 119 | 112 | | -| BoxedInt::operator int | true | 121 | 116 | | -| BoxedInt::operator int | true | 123 | 121 | | -| BoxedInt::operator= | false | 132 | 132 | operator= | -| BoxedInt::~BoxedInt | false | 93 | 93 | ~BoxedInt | -| BoxedInt::~BoxedInt | false | 100 | 100 | this | -| BoxedInt::~BoxedInt | false | 101 | 101 | m_ptr | -| BoxedInt::~BoxedInt | false | 103 | 103 | delete | -| BoxedInt::~BoxedInt | false | 105 | 105 | ExprStmt | -| BoxedInt::~BoxedInt | false | 107 | 107 | return ... | -| BoxedInt::~BoxedInt | false | 109 | 109 | { ... } | -| BoxedInt::~BoxedInt | true | 100 | 101 | | -| BoxedInt::~BoxedInt | true | 101 | 103 | | -| BoxedInt::~BoxedInt | true | 103 | 107 | | -| BoxedInt::~BoxedInt | true | 105 | 100 | | -| BoxedInt::~BoxedInt | true | 107 | 93 | | -| BoxedInt::~BoxedInt | true | 109 | 105 | | -| NonTrivial::operator= | false | 799 | 799 | operator= | -| NonTrivial::~NonTrivial | false | 480 | 480 | ~NonTrivial | -| NonTrivial::~NonTrivial | false | 788 | 788 | return ... | -| NonTrivial::~NonTrivial | false | 790 | 790 | { ... } | -| NonTrivial::~NonTrivial | true | 788 | 480 | | -| NonTrivial::~NonTrivial | true | 790 | 788 | | -| __va_list_tag::operator= | false | 54 | 54 | operator= | -| __va_list_tag::operator= | false | 60 | 60 | operator= | -| early_return | false | 506 | 506 | early_return | -| early_return | false | 513 | 513 | declaration | -| early_return | false | 515 | 515 | x | -| early_return | false | 517 | 517 | (bool)... | -| early_return | false | 522 | 522 | declaration | -| early_return | false | 524 | 524 | return ... | -| early_return | false | 526 | 526 | { ... } | -| early_return | false | 528 | 528 | if (...) ... | -| early_return | false | 533 | 533 | declaration | -| early_return | false | 535 | 535 | return ... | -| early_return | false | 537 | 537 | { ... } | -| early_return | false | 539 | 539 | before | -| early_return | false | 541 | 541 | call to before.~NonTrivial | -| early_return | false | 542 | 542 | after | -| early_return | false | 543 | 543 | call to after.~NonTrivial | -| early_return | false | 544 | 544 | inner | -| early_return | false | 546 | 546 | call to inner.~NonTrivial | -| early_return | false | 547 | 547 | inner | -| early_return | false | 548 | 548 | call to inner.~NonTrivial | -| early_return | true | 513 | 528 | | -| early_return | true | 515 | 526 | T | -| early_return | true | 515 | 533 | F | -| early_return | true | 522 | 524 | | -| early_return | true | 524 | 547 | | -| early_return | true | 526 | 522 | | -| early_return | true | 528 | 515 | | -| early_return | true | 533 | 535 | | -| early_return | true | 535 | 542 | | -| early_return | true | 537 | 513 | | -| early_return | true | 539 | 541 | | -| early_return | true | 541 | 506 | | -| early_return | true | 542 | 543 | | -| early_return | true | 543 | 539 | | -| early_return | true | 544 | 546 | | -| early_return | true | 546 | 533 | | -| early_return | true | 547 | 548 | | -| early_return | true | 548 | 539 | | -| early_throw | false | 551 | 551 | early_throw | -| early_throw | false | 558 | 558 | declaration | -| early_throw | false | 560 | 560 | x | -| early_throw | false | 562 | 562 | (bool)... | -| early_throw | false | 567 | 567 | declaration | -| early_throw | false | 569 | 569 | re-throw exception | -| early_throw | false | 571 | 571 | ExprStmt | -| early_throw | false | 573 | 573 | { ... } | -| early_throw | false | 575 | 575 | if (...) ... | -| early_throw | false | 580 | 580 | declaration | -| early_throw | false | 582 | 582 | return ... | -| early_throw | false | 584 | 584 | { ... } | -| early_throw | false | 586 | 586 | before | -| early_throw | false | 588 | 588 | call to before.~NonTrivial | -| early_throw | false | 589 | 589 | after | -| early_throw | false | 590 | 590 | call to after.~NonTrivial | -| early_throw | false | 591 | 591 | inner | -| early_throw | false | 593 | 593 | call to inner.~NonTrivial | -| early_throw | false | 594 | 594 | before | -| early_throw | false | 595 | 595 | call to before.~NonTrivial | -| early_throw | false | 596 | 596 | inner | -| early_throw | false | 597 | 597 | call to inner.~NonTrivial | -| early_throw | true | 558 | 575 | | -| early_throw | true | 560 | 573 | T | -| early_throw | true | 560 | 580 | F | -| early_throw | true | 567 | 571 | | -| early_throw | true | 569 | 596 | | -| early_throw | true | 571 | 569 | | -| early_throw | true | 573 | 567 | | -| early_throw | true | 575 | 560 | | -| early_throw | true | 580 | 582 | | -| early_throw | true | 582 | 589 | | -| early_throw | true | 584 | 558 | | -| early_throw | true | 586 | 588 | | -| early_throw | true | 588 | 551 | | -| early_throw | true | 589 | 590 | | -| early_throw | true | 590 | 586 | | -| early_throw | true | 591 | 593 | | -| early_throw | true | 593 | 580 | | -| early_throw | true | 594 | 595 | | -| early_throw | true | 595 | 551 | | -| early_throw | true | 596 | 597 | | -| early_throw | true | 597 | 594 | | -| for_decl_bind | false | 353 | 353 | for_decl_bind | -| for_decl_bind | false | 358 | 358 | call to BoxedInt | -| for_decl_bind | false | 360 | 360 | x | -| for_decl_bind | false | 362 | 362 | - ... | -| for_decl_bind | false | 364 | 364 | initializer for init | -| for_decl_bind | false | 369 | 369 | call to operator int | -| for_decl_bind | false | 371 | 371 | call to BoxedInt | -| for_decl_bind | false | 373 | 373 | x | -| for_decl_bind | false | 375 | 375 | initializer for bi | -| for_decl_bind | false | 379 | 379 | bi | -| for_decl_bind | false | 381 | 381 | (bool)... | -| for_decl_bind | false | 382 | 382 | (condition decl) | -| for_decl_bind | false | 384 | 384 | x | -| for_decl_bind | false | 386 | 386 | ++ ... | -| for_decl_bind | false | 388 | 388 | ExprStmt | -| for_decl_bind | false | 390 | 390 | { ... } | -| for_decl_bind | false | 392 | 392 | declaration | -| for_decl_bind | false | 394 | 394 | x | -| for_decl_bind | false | 398 | 398 | 2 | -| for_decl_bind | false | 399 | 399 | ... *= ... | -| for_decl_bind | false | 401 | 401 | for(...;...;...) ... | -| for_decl_bind | false | 403 | 403 | x | -| for_decl_bind | false | 405 | 405 | -- ... | -| for_decl_bind | false | 407 | 407 | ExprStmt | -| for_decl_bind | false | 409 | 409 | return ... | -| for_decl_bind | false | 411 | 411 | { ... } | -| for_decl_bind | false | 413 | 413 | init | -| for_decl_bind | false | 415 | 415 | call to init.~BoxedInt | -| for_decl_bind | false | 416 | 416 | bi | -| for_decl_bind | false | 418 | 418 | call to bi.~BoxedInt | -| for_decl_bind | false | 419 | 419 | bi | -| for_decl_bind | false | 420 | 420 | call to bi.~BoxedInt | -| for_decl_bind | true | 358 | 373 | | -| for_decl_bind | true | 360 | 362 | | -| for_decl_bind | true | 362 | 358 | | -| for_decl_bind | true | 364 | 360 | | -| for_decl_bind | true | 371 | 382 | | -| for_decl_bind | true | 373 | 371 | | -| for_decl_bind | true | 382 | 390 | T | -| for_decl_bind | true | 382 | 416 | F | -| for_decl_bind | true | 384 | 386 | | -| for_decl_bind | true | 386 | 394 | | -| for_decl_bind | true | 388 | 384 | | -| for_decl_bind | true | 390 | 388 | | -| for_decl_bind | true | 392 | 364 | | -| for_decl_bind | true | 394 | 398 | | -| for_decl_bind | true | 398 | 399 | | -| for_decl_bind | true | 399 | 419 | | -| for_decl_bind | true | 401 | 392 | | -| for_decl_bind | true | 403 | 405 | | -| for_decl_bind | true | 405 | 409 | | -| for_decl_bind | true | 407 | 403 | | -| for_decl_bind | true | 409 | 353 | | -| for_decl_bind | true | 411 | 401 | | -| for_decl_bind | true | 413 | 415 | | -| for_decl_bind | true | 415 | 407 | | -| for_decl_bind | true | 416 | 418 | | -| for_decl_bind | true | 418 | 413 | | -| for_decl_bind | true | 419 | 420 | | -| for_decl_bind | true | 420 | 373 | | -| for_loop_scope | false | 600 | 600 | for_loop_scope | -| for_loop_scope | false | 607 | 607 | declaration | -| for_loop_scope | false | 612 | 612 | x | -| for_loop_scope | false | 616 | 616 | 10 | -| for_loop_scope | false | 617 | 617 | ... < ... | -| for_loop_scope | false | 622 | 622 | declaration | -| for_loop_scope | false | 624 | 624 | { ... } | -| for_loop_scope | false | 626 | 626 | declaration | -| for_loop_scope | false | 628 | 628 | x | -| for_loop_scope | false | 630 | 630 | ++ ... | -| for_loop_scope | false | 632 | 632 | for(...;...;...) ... | -| for_loop_scope | false | 634 | 634 | return ... | -| for_loop_scope | false | 636 | 636 | { ... } | -| for_loop_scope | false | 638 | 638 | outer_scope | -| for_loop_scope | false | 640 | 640 | call to outer_scope.~NonTrivial | -| for_loop_scope | false | 641 | 641 | for_scope | -| for_loop_scope | false | 643 | 643 | call to for_scope.~NonTrivial | -| for_loop_scope | false | 644 | 644 | inner_scope | -| for_loop_scope | false | 646 | 646 | call to inner_scope.~NonTrivial | -| for_loop_scope | true | 607 | 632 | | -| for_loop_scope | true | 612 | 616 | | -| for_loop_scope | true | 616 | 617 | | -| for_loop_scope | true | 617 | 624 | T | -| for_loop_scope | true | 617 | 641 | F | -| for_loop_scope | true | 622 | 644 | | -| for_loop_scope | true | 624 | 622 | | -| for_loop_scope | true | 626 | 612 | | -| for_loop_scope | true | 628 | 630 | | -| for_loop_scope | true | 630 | 612 | | -| for_loop_scope | true | 632 | 626 | | -| for_loop_scope | true | 634 | 638 | | -| for_loop_scope | true | 636 | 607 | | -| for_loop_scope | true | 638 | 640 | | -| for_loop_scope | true | 640 | 600 | | -| for_loop_scope | true | 641 | 643 | | -| for_loop_scope | true | 643 | 634 | | -| for_loop_scope | true | 644 | 646 | | -| for_loop_scope | true | 646 | 628 | | -| gotos | false | 664 | 664 | gotos | -| gotos | false | 674 | 674 | declaration | -| gotos | false | 676 | 676 | x | -| gotos | false | 678 | 678 | (bool)... | -| gotos | false | 682 | 682 | goto ... | -| gotos | false | 684 | 684 | if (...) ... | -| gotos | false | 686 | 686 | x | -| gotos | false | 688 | 688 | ++ ... | -| gotos | false | 690 | 690 | initializer for y | -| gotos | false | 698 | 698 | declaration | -| gotos | false | 700 | 700 | label ...: | -| gotos | false | 702 | 702 | declaration | -| gotos | false | 704 | 704 | y | -| gotos | false | 706 | 706 | (bool)... | -| gotos | false | 710 | 710 | goto ... | -| gotos | false | 712 | 712 | if (...) ... | -| gotos | false | 714 | 714 | declaration | -| gotos | false | 716 | 716 | { ... } | -| gotos | false | 718 | 718 | label ...: | -| gotos | false | 720 | 720 | x | -| gotos | false | 722 | 722 | -- ... | -| gotos | false | 724 | 724 | ExprStmt | -| gotos | false | 726 | 726 | return ... | -| gotos | false | 728 | 728 | { ... } | -| gotos | false | 730 | 730 | nt1 | -| gotos | false | 732 | 732 | call to nt1.~NonTrivial | -| gotos | false | 733 | 733 | nt2 | -| gotos | false | 735 | 735 | call to nt2.~NonTrivial | -| gotos | false | 736 | 736 | nt3 | -| gotos | false | 737 | 737 | call to nt3.~NonTrivial | -| gotos | false | 738 | 738 | nt2 | -| gotos | false | 739 | 739 | call to nt2.~NonTrivial | -| gotos | true | 674 | 684 | | -| gotos | true | 676 | 682 | T | -| gotos | true | 676 | 716 | F | -| gotos | true | 682 | 700 | | -| gotos | true | 684 | 676 | | -| gotos | true | 686 | 688 | | -| gotos | true | 688 | 700 | | -| gotos | true | 690 | 686 | | -| gotos | true | 698 | 690 | | -| gotos | true | 698 | 700 | | -| gotos | true | 700 | 702 | | -| gotos | true | 702 | 712 | | -| gotos | true | 704 | 710 | T | -| gotos | true | 704 | 714 | F | -| gotos | true | 710 | 738 | | -| gotos | true | 712 | 704 | | -| gotos | true | 714 | 736 | | -| gotos | true | 716 | 698 | | -| gotos | true | 718 | 724 | | -| gotos | true | 720 | 722 | | -| gotos | true | 722 | 726 | | -| gotos | true | 724 | 720 | | -| gotos | true | 726 | 730 | | -| gotos | true | 728 | 674 | | -| gotos | true | 730 | 732 | | -| gotos | true | 732 | 664 | | -| gotos | true | 733 | 735 | | -| gotos | true | 735 | 718 | | -| gotos | true | 736 | 737 | | -| gotos | true | 737 | 733 | | -| gotos | true | 738 | 739 | | -| gotos | true | 739 | 718 | | -| if_decl_bind | false | 144 | 144 | if_decl_bind | -| if_decl_bind | false | 151 | 151 | call to operator int | -| if_decl_bind | false | 153 | 153 | call to BoxedInt | -| if_decl_bind | false | 155 | 155 | x | -| if_decl_bind | false | 157 | 157 | initializer for bi | -| if_decl_bind | false | 161 | 161 | bi | -| if_decl_bind | false | 163 | 163 | (bool)... | -| if_decl_bind | false | 164 | 164 | (condition decl) | -| if_decl_bind | false | 166 | 166 | bi | -| if_decl_bind | false | 168 | 168 | m_ptr | -| if_decl_bind | false | 170 | 170 | * ... | -| if_decl_bind | false | 172 | 172 | ++ ... | -| if_decl_bind | false | 174 | 174 | ExprStmt | -| if_decl_bind | false | 176 | 176 | { ... } | -| if_decl_bind | false | 178 | 178 | bi | -| if_decl_bind | false | 180 | 180 | m_ptr | -| if_decl_bind | false | 182 | 182 | * ... | -| if_decl_bind | false | 184 | 184 | -- ... | -| if_decl_bind | false | 186 | 186 | ExprStmt | -| if_decl_bind | false | 188 | 188 | { ... } | -| if_decl_bind | false | 190 | 190 | if (...) ... | -| if_decl_bind | false | 192 | 192 | x | -| if_decl_bind | false | 196 | 196 | 1 | -| if_decl_bind | false | 197 | 197 | ... = ... | -| if_decl_bind | false | 199 | 199 | ExprStmt | -| if_decl_bind | false | 201 | 201 | return ... | -| if_decl_bind | false | 203 | 203 | { ... } | -| if_decl_bind | false | 205 | 205 | bi | -| if_decl_bind | false | 207 | 207 | call to bi.~BoxedInt | -| if_decl_bind | true | 153 | 164 | | -| if_decl_bind | true | 155 | 153 | | -| if_decl_bind | true | 164 | 176 | T | -| if_decl_bind | true | 164 | 188 | F | -| if_decl_bind | true | 166 | 168 | | -| if_decl_bind | true | 168 | 170 | | -| if_decl_bind | true | 170 | 172 | | -| if_decl_bind | true | 172 | 205 | | -| if_decl_bind | true | 174 | 166 | | -| if_decl_bind | true | 176 | 174 | | -| if_decl_bind | true | 178 | 180 | | -| if_decl_bind | true | 180 | 182 | | -| if_decl_bind | true | 182 | 184 | | -| if_decl_bind | true | 184 | 205 | | -| if_decl_bind | true | 186 | 178 | | -| if_decl_bind | true | 188 | 186 | | -| if_decl_bind | true | 190 | 155 | | -| if_decl_bind | true | 192 | 197 | | -| if_decl_bind | true | 196 | 192 | | -| if_decl_bind | true | 197 | 201 | | -| if_decl_bind | true | 199 | 196 | | -| if_decl_bind | true | 201 | 144 | | -| if_decl_bind | true | 203 | 190 | | -| if_decl_bind | true | 205 | 207 | | -| if_decl_bind | true | 207 | 199 | | -| never_destructs | false | 648 | 648 | never_destructs | -| never_destructs | false | 654 | 654 | declaration | -| never_destructs | false | 656 | 656 | label ...: | -| never_destructs | false | 658 | 658 | goto ... | -| never_destructs | false | 660 | 660 | { ... } | -| never_destructs | true | 654 | 656 | | -| never_destructs | true | 656 | 658 | | -| never_destructs | true | 658 | 656 | | -| never_destructs | true | 660 | 654 | | -| operator delete | false | 97 | 97 | operator delete | -| operator new | false | 78 | 78 | operator new | -| simple | false | 464 | 464 | simple | -| simple | false | 471 | 471 | declaration | -| simple | false | 473 | 473 | return ... | -| simple | false | 475 | 475 | { ... } | -| simple | false | 477 | 477 | nt | -| simple | false | 479 | 479 | call to nt.~NonTrivial | -| simple | true | 471 | 473 | | -| simple | true | 473 | 477 | | -| simple | true | 475 | 471 | | -| simple | true | 477 | 479 | | -| simple | true | 479 | 464 | | -| simple2 | false | 482 | 482 | simple2 | -| simple2 | false | 488 | 488 | declaration | -| simple2 | false | 493 | 493 | declaration | -| simple2 | false | 495 | 495 | return ... | -| simple2 | false | 497 | 497 | { ... } | -| simple2 | false | 499 | 499 | one | -| simple2 | false | 501 | 501 | call to one.~NonTrivial | -| simple2 | false | 502 | 502 | two | -| simple2 | false | 503 | 503 | call to two.~NonTrivial | -| simple2 | true | 488 | 493 | | -| simple2 | true | 493 | 495 | | -| simple2 | true | 495 | 502 | | -| simple2 | true | 497 | 488 | | -| simple2 | true | 499 | 501 | | -| simple2 | true | 501 | 482 | | -| simple2 | true | 502 | 503 | | -| simple2 | true | 503 | 499 | | -| switch_decl_bind | false | 210 | 210 | switch_decl_bind | -| switch_decl_bind | false | 215 | 215 | call to operator int | -| switch_decl_bind | false | 217 | 217 | call to BoxedInt | -| switch_decl_bind | false | 219 | 219 | x | -| switch_decl_bind | false | 221 | 221 | initializer for bi | -| switch_decl_bind | false | 225 | 225 | bi | -| switch_decl_bind | false | 227 | 227 | (condition decl) | -| switch_decl_bind | false | 231 | 231 | 0 | -| switch_decl_bind | false | 232 | 232 | case ...: | -| switch_decl_bind | false | 234 | 234 | bi | -| switch_decl_bind | false | 236 | 236 | m_ptr | -| switch_decl_bind | false | 238 | 238 | * ... | -| switch_decl_bind | false | 240 | 240 | -- ... | -| switch_decl_bind | false | 242 | 242 | ExprStmt | -| switch_decl_bind | false | 244 | 244 | break; | -| switch_decl_bind | false | 248 | 248 | 1 | -| switch_decl_bind | false | 249 | 249 | case ...: | -| switch_decl_bind | false | 251 | 251 | bi | -| switch_decl_bind | false | 253 | 253 | m_ptr | -| switch_decl_bind | false | 255 | 255 | * ... | -| switch_decl_bind | false | 257 | 257 | ++ ... | -| switch_decl_bind | false | 259 | 259 | ExprStmt | -| switch_decl_bind | false | 261 | 261 | break; | -| switch_decl_bind | false | 263 | 263 | default: | -| switch_decl_bind | false | 265 | 265 | bi | -| switch_decl_bind | false | 267 | 267 | m_ptr | -| switch_decl_bind | false | 269 | 269 | * ... | -| switch_decl_bind | false | 273 | 273 | 2 | -| switch_decl_bind | false | 274 | 274 | ... /= ... | -| switch_decl_bind | false | 276 | 276 | ExprStmt | -| switch_decl_bind | false | 278 | 278 | { ... } | -| switch_decl_bind | false | 280 | 280 | switch (...) ... | -| switch_decl_bind | false | 282 | 282 | label ...: | -| switch_decl_bind | false | 284 | 284 | x | -| switch_decl_bind | false | 288 | 288 | 1 | -| switch_decl_bind | false | 289 | 289 | ... = ... | -| switch_decl_bind | false | 291 | 291 | ExprStmt | -| switch_decl_bind | false | 293 | 293 | return ... | -| switch_decl_bind | false | 295 | 295 | { ... } | -| switch_decl_bind | false | 297 | 297 | bi | -| switch_decl_bind | false | 299 | 299 | call to bi.~BoxedInt | -| switch_decl_bind | false | 300 | 300 | bi | -| switch_decl_bind | false | 301 | 301 | call to bi.~BoxedInt | -| switch_decl_bind | false | 302 | 302 | bi | -| switch_decl_bind | false | 303 | 303 | call to bi.~BoxedInt | -| switch_decl_bind | true | 217 | 227 | | -| switch_decl_bind | true | 219 | 217 | | -| switch_decl_bind | true | 227 | 278 | | -| switch_decl_bind | true | 232 | 242 | | -| switch_decl_bind | true | 234 | 236 | | -| switch_decl_bind | true | 236 | 238 | | -| switch_decl_bind | true | 238 | 240 | | -| switch_decl_bind | true | 240 | 244 | | -| switch_decl_bind | true | 242 | 234 | | -| switch_decl_bind | true | 244 | 302 | | -| switch_decl_bind | true | 249 | 259 | | -| switch_decl_bind | true | 251 | 253 | | -| switch_decl_bind | true | 253 | 255 | | -| switch_decl_bind | true | 255 | 257 | | -| switch_decl_bind | true | 257 | 261 | | -| switch_decl_bind | true | 259 | 251 | | -| switch_decl_bind | true | 261 | 300 | | -| switch_decl_bind | true | 263 | 276 | | -| switch_decl_bind | true | 265 | 267 | | -| switch_decl_bind | true | 267 | 269 | | -| switch_decl_bind | true | 269 | 273 | | -| switch_decl_bind | true | 273 | 274 | | -| switch_decl_bind | true | 274 | 297 | | -| switch_decl_bind | true | 276 | 265 | | -| switch_decl_bind | true | 278 | 232 | | -| switch_decl_bind | true | 278 | 249 | | -| switch_decl_bind | true | 278 | 263 | | -| switch_decl_bind | true | 280 | 219 | | -| switch_decl_bind | true | 282 | 291 | | -| switch_decl_bind | true | 284 | 289 | | -| switch_decl_bind | true | 288 | 284 | | -| switch_decl_bind | true | 289 | 293 | | -| switch_decl_bind | true | 291 | 288 | | -| switch_decl_bind | true | 293 | 210 | | -| switch_decl_bind | true | 295 | 280 | | -| switch_decl_bind | true | 297 | 299 | | -| switch_decl_bind | true | 299 | 282 | | -| switch_decl_bind | true | 300 | 301 | | -| switch_decl_bind | true | 301 | 282 | | -| switch_decl_bind | true | 302 | 303 | | -| switch_decl_bind | true | 303 | 282 | | -| while_decl_bind | false | 306 | 306 | while_decl_bind | -| while_decl_bind | false | 311 | 311 | call to operator int | -| while_decl_bind | false | 313 | 313 | call to BoxedInt | -| while_decl_bind | false | 315 | 315 | x | -| while_decl_bind | false | 317 | 317 | initializer for bi | -| while_decl_bind | false | 321 | 321 | bi | -| while_decl_bind | false | 323 | 323 | (bool)... | -| while_decl_bind | false | 324 | 324 | (condition decl) | -| while_decl_bind | false | 326 | 326 | x | -| while_decl_bind | false | 328 | 328 | -- ... | -| while_decl_bind | false | 330 | 330 | ExprStmt | -| while_decl_bind | false | 332 | 332 | { ... } | -| while_decl_bind | false | 334 | 334 | while (...) ... | -| while_decl_bind | false | 336 | 336 | x | -| while_decl_bind | false | 338 | 338 | ++ ... | -| while_decl_bind | false | 340 | 340 | ExprStmt | -| while_decl_bind | false | 342 | 342 | return ... | -| while_decl_bind | false | 344 | 344 | { ... } | -| while_decl_bind | false | 346 | 346 | bi | -| while_decl_bind | false | 348 | 348 | call to bi.~BoxedInt | -| while_decl_bind | false | 349 | 349 | bi | -| while_decl_bind | false | 350 | 350 | call to bi.~BoxedInt | -| while_decl_bind | true | 313 | 324 | | -| while_decl_bind | true | 315 | 313 | | -| while_decl_bind | true | 324 | 332 | T | -| while_decl_bind | true | 324 | 346 | F | -| while_decl_bind | true | 326 | 328 | | -| while_decl_bind | true | 328 | 349 | | -| while_decl_bind | true | 330 | 326 | | -| while_decl_bind | true | 332 | 330 | | -| while_decl_bind | true | 334 | 315 | | -| while_decl_bind | true | 336 | 338 | | -| while_decl_bind | true | 338 | 342 | | -| while_decl_bind | true | 340 | 336 | | -| while_decl_bind | true | 342 | 306 | | -| while_decl_bind | true | 344 | 334 | | -| while_decl_bind | true | 346 | 348 | | -| while_decl_bind | true | 348 | 340 | | -| while_decl_bind | true | 349 | 350 | | -| while_decl_bind | true | 350 | 315 | | +| BoxedInt::BoxedInt | false | 196 | 196 | BoxedInt | +| BoxedInt::BoxedInt | false | 484 | 484 | BoxedInt | +| BoxedInt::BoxedInt | false | 527 | 527 | this | +| BoxedInt::BoxedInt | false | 528 | 528 | m_ptr | +| BoxedInt::BoxedInt | false | 532 | 532 | x | +| BoxedInt::BoxedInt | false | 534 | 534 | new | +| BoxedInt::BoxedInt | false | 536 | 536 | ... = ... | +| BoxedInt::BoxedInt | false | 538 | 538 | ExprStmt | +| BoxedInt::BoxedInt | false | 540 | 540 | return ... | +| BoxedInt::BoxedInt | false | 542 | 542 | { ... } | +| BoxedInt::BoxedInt | true | 527 | 528 | | +| BoxedInt::BoxedInt | true | 528 | 536 | | +| BoxedInt::BoxedInt | true | 532 | 534 | | +| BoxedInt::BoxedInt | true | 534 | 527 | | +| BoxedInt::BoxedInt | true | 536 | 540 | | +| BoxedInt::BoxedInt | true | 538 | 532 | | +| BoxedInt::BoxedInt | true | 540 | 196 | | +| BoxedInt::BoxedInt | true | 542 | 538 | | +| BoxedInt::operator int | false | 204 | 204 | operator int | +| BoxedInt::operator int | false | 494 | 494 | this | +| BoxedInt::operator int | false | 495 | 495 | m_ptr | +| BoxedInt::operator int | false | 497 | 497 | * ... | +| BoxedInt::operator int | false | 499 | 499 | return ... | +| BoxedInt::operator int | false | 501 | 501 | { ... } | +| BoxedInt::operator int | true | 494 | 495 | | +| BoxedInt::operator int | true | 495 | 497 | | +| BoxedInt::operator int | true | 497 | 204 | | +| BoxedInt::operator int | true | 499 | 494 | | +| BoxedInt::operator int | true | 501 | 499 | | +| BoxedInt::operator= | false | 477 | 477 | operator= | +| BoxedInt::~BoxedInt | false | 254 | 254 | ~BoxedInt | +| BoxedInt::~BoxedInt | false | 509 | 509 | this | +| BoxedInt::~BoxedInt | false | 510 | 510 | m_ptr | +| BoxedInt::~BoxedInt | false | 512 | 512 | delete | +| BoxedInt::~BoxedInt | false | 514 | 514 | ExprStmt | +| BoxedInt::~BoxedInt | false | 516 | 516 | return ... | +| BoxedInt::~BoxedInt | false | 518 | 518 | { ... } | +| BoxedInt::~BoxedInt | true | 509 | 510 | | +| BoxedInt::~BoxedInt | true | 510 | 512 | | +| BoxedInt::~BoxedInt | true | 512 | 516 | | +| BoxedInt::~BoxedInt | true | 514 | 509 | | +| BoxedInt::~BoxedInt | true | 516 | 254 | | +| BoxedInt::~BoxedInt | true | 518 | 514 | | +| NonTrivial::operator= | false | 875 | 875 | operator= | +| NonTrivial::~NonTrivial | false | 649 | 649 | ~NonTrivial | +| NonTrivial::~NonTrivial | false | 886 | 886 | return ... | +| NonTrivial::~NonTrivial | false | 888 | 888 | { ... } | +| NonTrivial::~NonTrivial | true | 886 | 649 | | +| NonTrivial::~NonTrivial | true | 888 | 886 | | +| __va_list_tag::operator= | false | 95 | 95 | operator= | +| __va_list_tag::operator= | false | 102 | 102 | operator= | +| early_return | false | 777 | 777 | early_return | +| early_return | false | 785 | 785 | declaration | +| early_return | false | 787 | 787 | x | +| early_return | false | 789 | 789 | (bool)... | +| early_return | false | 793 | 793 | declaration | +| early_return | false | 795 | 795 | return ... | +| early_return | false | 797 | 797 | { ... } | +| early_return | false | 799 | 799 | if (...) ... | +| early_return | false | 801 | 801 | declaration | +| early_return | false | 803 | 803 | return ... | +| early_return | false | 805 | 805 | { ... } | +| early_return | false | 807 | 807 | before | +| early_return | false | 809 | 809 | call to before.~NonTrivial | +| early_return | false | 810 | 810 | after | +| early_return | false | 811 | 811 | call to after.~NonTrivial | +| early_return | false | 812 | 812 | inner | +| early_return | false | 814 | 814 | call to inner.~NonTrivial | +| early_return | false | 815 | 815 | inner | +| early_return | false | 816 | 816 | call to inner.~NonTrivial | +| early_return | true | 785 | 799 | | +| early_return | true | 787 | 797 | T | +| early_return | true | 787 | 801 | F | +| early_return | true | 793 | 795 | | +| early_return | true | 795 | 815 | | +| early_return | true | 797 | 793 | | +| early_return | true | 799 | 787 | | +| early_return | true | 801 | 803 | | +| early_return | true | 803 | 810 | | +| early_return | true | 805 | 785 | | +| early_return | true | 807 | 809 | | +| early_return | true | 809 | 777 | | +| early_return | true | 810 | 811 | | +| early_return | true | 811 | 807 | | +| early_return | true | 812 | 814 | | +| early_return | true | 814 | 801 | | +| early_return | true | 815 | 816 | | +| early_return | true | 816 | 807 | | +| early_throw | false | 727 | 727 | early_throw | +| early_throw | false | 735 | 735 | declaration | +| early_throw | false | 737 | 737 | x | +| early_throw | false | 739 | 739 | (bool)... | +| early_throw | false | 743 | 743 | declaration | +| early_throw | false | 745 | 745 | re-throw exception | +| early_throw | false | 747 | 747 | ExprStmt | +| early_throw | false | 749 | 749 | { ... } | +| early_throw | false | 751 | 751 | if (...) ... | +| early_throw | false | 753 | 753 | declaration | +| early_throw | false | 755 | 755 | return ... | +| early_throw | false | 757 | 757 | { ... } | +| early_throw | false | 759 | 759 | before | +| early_throw | false | 761 | 761 | call to before.~NonTrivial | +| early_throw | false | 762 | 762 | after | +| early_throw | false | 763 | 763 | call to after.~NonTrivial | +| early_throw | false | 764 | 764 | inner | +| early_throw | false | 766 | 766 | call to inner.~NonTrivial | +| early_throw | false | 767 | 767 | before | +| early_throw | false | 768 | 768 | call to before.~NonTrivial | +| early_throw | false | 769 | 769 | inner | +| early_throw | false | 770 | 770 | call to inner.~NonTrivial | +| early_throw | true | 735 | 751 | | +| early_throw | true | 737 | 749 | T | +| early_throw | true | 737 | 753 | F | +| early_throw | true | 743 | 747 | | +| early_throw | true | 745 | 769 | | +| early_throw | true | 747 | 745 | | +| early_throw | true | 749 | 743 | | +| early_throw | true | 751 | 737 | | +| early_throw | true | 753 | 755 | | +| early_throw | true | 755 | 762 | | +| early_throw | true | 757 | 735 | | +| early_throw | true | 759 | 761 | | +| early_throw | true | 761 | 727 | | +| early_throw | true | 762 | 763 | | +| early_throw | true | 763 | 759 | | +| early_throw | true | 764 | 766 | | +| early_throw | true | 766 | 753 | | +| early_throw | true | 767 | 768 | | +| early_throw | true | 768 | 727 | | +| early_throw | true | 769 | 770 | | +| early_throw | true | 770 | 767 | | +| for_decl_bind | false | 185 | 185 | for_decl_bind | +| for_decl_bind | false | 194 | 194 | call to BoxedInt | +| for_decl_bind | false | 197 | 197 | x | +| for_decl_bind | false | 199 | 199 | - ... | +| for_decl_bind | false | 201 | 201 | initializer for init | +| for_decl_bind | false | 208 | 208 | call to operator int | +| for_decl_bind | false | 210 | 210 | call to BoxedInt | +| for_decl_bind | false | 212 | 212 | x | +| for_decl_bind | false | 214 | 214 | initializer for bi | +| for_decl_bind | false | 217 | 217 | bi | +| for_decl_bind | false | 219 | 219 | (bool)... | +| for_decl_bind | false | 220 | 220 | (condition decl) | +| for_decl_bind | false | 222 | 222 | x | +| for_decl_bind | false | 224 | 224 | ++ ... | +| for_decl_bind | false | 226 | 226 | ExprStmt | +| for_decl_bind | false | 228 | 228 | { ... } | +| for_decl_bind | false | 230 | 230 | declaration | +| for_decl_bind | false | 232 | 232 | x | +| for_decl_bind | false | 236 | 236 | 2 | +| for_decl_bind | false | 237 | 237 | ... *= ... | +| for_decl_bind | false | 239 | 239 | for(...;...;...) ... | +| for_decl_bind | false | 241 | 241 | x | +| for_decl_bind | false | 243 | 243 | -- ... | +| for_decl_bind | false | 245 | 245 | ExprStmt | +| for_decl_bind | false | 247 | 247 | return ... | +| for_decl_bind | false | 249 | 249 | { ... } | +| for_decl_bind | false | 251 | 251 | init | +| for_decl_bind | false | 253 | 253 | call to init.~BoxedInt | +| for_decl_bind | false | 255 | 255 | bi | +| for_decl_bind | false | 257 | 257 | call to bi.~BoxedInt | +| for_decl_bind | false | 258 | 258 | bi | +| for_decl_bind | false | 259 | 259 | call to bi.~BoxedInt | +| for_decl_bind | true | 194 | 212 | | +| for_decl_bind | true | 197 | 199 | | +| for_decl_bind | true | 199 | 194 | | +| for_decl_bind | true | 201 | 197 | | +| for_decl_bind | true | 210 | 220 | | +| for_decl_bind | true | 212 | 210 | | +| for_decl_bind | true | 220 | 228 | T | +| for_decl_bind | true | 220 | 255 | F | +| for_decl_bind | true | 222 | 224 | | +| for_decl_bind | true | 224 | 232 | | +| for_decl_bind | true | 226 | 222 | | +| for_decl_bind | true | 228 | 226 | | +| for_decl_bind | true | 230 | 201 | | +| for_decl_bind | true | 232 | 236 | | +| for_decl_bind | true | 236 | 237 | | +| for_decl_bind | true | 237 | 258 | | +| for_decl_bind | true | 239 | 230 | | +| for_decl_bind | true | 241 | 243 | | +| for_decl_bind | true | 243 | 247 | | +| for_decl_bind | true | 245 | 241 | | +| for_decl_bind | true | 247 | 185 | | +| for_decl_bind | true | 249 | 239 | | +| for_decl_bind | true | 251 | 253 | | +| for_decl_bind | true | 253 | 245 | | +| for_decl_bind | true | 255 | 257 | | +| for_decl_bind | true | 257 | 251 | | +| for_decl_bind | true | 258 | 259 | | +| for_decl_bind | true | 259 | 212 | | +| for_loop_scope | false | 676 | 676 | for_loop_scope | +| for_loop_scope | false | 684 | 684 | declaration | +| for_loop_scope | false | 689 | 689 | x | +| for_loop_scope | false | 693 | 693 | 10 | +| for_loop_scope | false | 694 | 694 | ... < ... | +| for_loop_scope | false | 699 | 699 | declaration | +| for_loop_scope | false | 701 | 701 | { ... } | +| for_loop_scope | false | 703 | 703 | declaration | +| for_loop_scope | false | 705 | 705 | x | +| for_loop_scope | false | 707 | 707 | ++ ... | +| for_loop_scope | false | 709 | 709 | for(...;...;...) ... | +| for_loop_scope | false | 711 | 711 | return ... | +| for_loop_scope | false | 713 | 713 | { ... } | +| for_loop_scope | false | 715 | 715 | outer_scope | +| for_loop_scope | false | 717 | 717 | call to outer_scope.~NonTrivial | +| for_loop_scope | false | 718 | 718 | for_scope | +| for_loop_scope | false | 720 | 720 | call to for_scope.~NonTrivial | +| for_loop_scope | false | 721 | 721 | inner_scope | +| for_loop_scope | false | 723 | 723 | call to inner_scope.~NonTrivial | +| for_loop_scope | true | 684 | 709 | | +| for_loop_scope | true | 689 | 693 | | +| for_loop_scope | true | 693 | 694 | | +| for_loop_scope | true | 694 | 701 | T | +| for_loop_scope | true | 694 | 718 | F | +| for_loop_scope | true | 699 | 721 | | +| for_loop_scope | true | 701 | 699 | | +| for_loop_scope | true | 703 | 689 | | +| for_loop_scope | true | 705 | 707 | | +| for_loop_scope | true | 707 | 689 | | +| for_loop_scope | true | 709 | 703 | | +| for_loop_scope | true | 711 | 715 | | +| for_loop_scope | true | 713 | 684 | | +| for_loop_scope | true | 715 | 717 | | +| for_loop_scope | true | 717 | 676 | | +| for_loop_scope | true | 718 | 720 | | +| for_loop_scope | true | 720 | 711 | | +| for_loop_scope | true | 721 | 723 | | +| for_loop_scope | true | 723 | 705 | | +| gotos | false | 585 | 585 | gotos | +| gotos | false | 593 | 593 | declaration | +| gotos | false | 595 | 595 | x | +| gotos | false | 597 | 597 | (bool)... | +| gotos | false | 598 | 598 | goto ... | +| gotos | false | 600 | 600 | if (...) ... | +| gotos | false | 602 | 602 | x | +| gotos | false | 604 | 604 | ++ ... | +| gotos | false | 606 | 606 | initializer for y | +| gotos | false | 617 | 617 | declaration | +| gotos | false | 619 | 619 | label ...: | +| gotos | false | 621 | 621 | declaration | +| gotos | false | 623 | 623 | y | +| gotos | false | 625 | 625 | (bool)... | +| gotos | false | 626 | 626 | goto ... | +| gotos | false | 628 | 628 | if (...) ... | +| gotos | false | 630 | 630 | declaration | +| gotos | false | 632 | 632 | { ... } | +| gotos | false | 634 | 634 | label ...: | +| gotos | false | 636 | 636 | x | +| gotos | false | 638 | 638 | -- ... | +| gotos | false | 640 | 640 | ExprStmt | +| gotos | false | 642 | 642 | return ... | +| gotos | false | 644 | 644 | { ... } | +| gotos | false | 646 | 646 | nt1 | +| gotos | false | 648 | 648 | call to nt1.~NonTrivial | +| gotos | false | 650 | 650 | nt2 | +| gotos | false | 652 | 652 | call to nt2.~NonTrivial | +| gotos | false | 653 | 653 | nt3 | +| gotos | false | 654 | 654 | call to nt3.~NonTrivial | +| gotos | false | 655 | 655 | nt2 | +| gotos | false | 656 | 656 | call to nt2.~NonTrivial | +| gotos | true | 593 | 600 | | +| gotos | true | 595 | 598 | T | +| gotos | true | 595 | 632 | F | +| gotos | true | 598 | 619 | | +| gotos | true | 600 | 595 | | +| gotos | true | 602 | 604 | | +| gotos | true | 604 | 619 | | +| gotos | true | 606 | 602 | | +| gotos | true | 617 | 606 | | +| gotos | true | 617 | 619 | | +| gotos | true | 619 | 621 | | +| gotos | true | 621 | 628 | | +| gotos | true | 623 | 626 | T | +| gotos | true | 623 | 630 | F | +| gotos | true | 626 | 655 | | +| gotos | true | 628 | 623 | | +| gotos | true | 630 | 653 | | +| gotos | true | 632 | 617 | | +| gotos | true | 634 | 640 | | +| gotos | true | 636 | 638 | | +| gotos | true | 638 | 642 | | +| gotos | true | 640 | 636 | | +| gotos | true | 642 | 646 | | +| gotos | true | 644 | 593 | | +| gotos | true | 646 | 648 | | +| gotos | true | 648 | 585 | | +| gotos | true | 650 | 652 | | +| gotos | true | 652 | 634 | | +| gotos | true | 653 | 654 | | +| gotos | true | 654 | 650 | | +| gotos | true | 655 | 656 | | +| gotos | true | 656 | 634 | | +| if_decl_bind | false | 407 | 407 | if_decl_bind | +| if_decl_bind | false | 416 | 416 | call to operator int | +| if_decl_bind | false | 418 | 418 | call to BoxedInt | +| if_decl_bind | false | 420 | 420 | x | +| if_decl_bind | false | 422 | 422 | initializer for bi | +| if_decl_bind | false | 425 | 425 | bi | +| if_decl_bind | false | 427 | 427 | (bool)... | +| if_decl_bind | false | 428 | 428 | (condition decl) | +| if_decl_bind | false | 430 | 430 | bi | +| if_decl_bind | false | 432 | 432 | m_ptr | +| if_decl_bind | false | 434 | 434 | * ... | +| if_decl_bind | false | 436 | 436 | ++ ... | +| if_decl_bind | false | 438 | 438 | ExprStmt | +| if_decl_bind | false | 440 | 440 | { ... } | +| if_decl_bind | false | 442 | 442 | bi | +| if_decl_bind | false | 444 | 444 | m_ptr | +| if_decl_bind | false | 446 | 446 | * ... | +| if_decl_bind | false | 448 | 448 | -- ... | +| if_decl_bind | false | 450 | 450 | ExprStmt | +| if_decl_bind | false | 452 | 452 | { ... } | +| if_decl_bind | false | 454 | 454 | if (...) ... | +| if_decl_bind | false | 456 | 456 | x | +| if_decl_bind | false | 460 | 460 | 1 | +| if_decl_bind | false | 461 | 461 | ... = ... | +| if_decl_bind | false | 463 | 463 | ExprStmt | +| if_decl_bind | false | 465 | 465 | return ... | +| if_decl_bind | false | 467 | 467 | { ... } | +| if_decl_bind | false | 469 | 469 | bi | +| if_decl_bind | false | 471 | 471 | call to bi.~BoxedInt | +| if_decl_bind | true | 418 | 428 | | +| if_decl_bind | true | 420 | 418 | | +| if_decl_bind | true | 428 | 440 | T | +| if_decl_bind | true | 428 | 452 | F | +| if_decl_bind | true | 430 | 432 | | +| if_decl_bind | true | 432 | 434 | | +| if_decl_bind | true | 434 | 436 | | +| if_decl_bind | true | 436 | 469 | | +| if_decl_bind | true | 438 | 430 | | +| if_decl_bind | true | 440 | 438 | | +| if_decl_bind | true | 442 | 444 | | +| if_decl_bind | true | 444 | 446 | | +| if_decl_bind | true | 446 | 448 | | +| if_decl_bind | true | 448 | 469 | | +| if_decl_bind | true | 450 | 442 | | +| if_decl_bind | true | 452 | 450 | | +| if_decl_bind | true | 454 | 420 | | +| if_decl_bind | true | 456 | 461 | | +| if_decl_bind | true | 460 | 456 | | +| if_decl_bind | true | 461 | 465 | | +| if_decl_bind | true | 463 | 460 | | +| if_decl_bind | true | 465 | 407 | | +| if_decl_bind | true | 467 | 454 | | +| if_decl_bind | true | 469 | 471 | | +| if_decl_bind | true | 471 | 463 | | +| never_destructs | false | 660 | 660 | never_destructs | +| never_destructs | false | 665 | 665 | declaration | +| never_destructs | false | 667 | 667 | label ...: | +| never_destructs | false | 669 | 669 | goto ... | +| never_destructs | false | 671 | 671 | { ... } | +| never_destructs | true | 665 | 667 | | +| never_destructs | true | 667 | 669 | | +| never_destructs | true | 669 | 667 | | +| never_destructs | true | 671 | 665 | | +| operator delete | false | 507 | 507 | operator delete | +| operator new | false | 530 | 530 | operator new | +| simple | false | 847 | 847 | simple | +| simple | false | 852 | 852 | declaration | +| simple | false | 854 | 854 | return ... | +| simple | false | 856 | 856 | { ... } | +| simple | false | 858 | 858 | nt | +| simple | false | 860 | 860 | call to nt.~NonTrivial | +| simple | true | 852 | 854 | | +| simple | true | 854 | 858 | | +| simple | true | 856 | 852 | | +| simple | true | 858 | 860 | | +| simple | true | 860 | 847 | | +| simple2 | false | 823 | 823 | simple2 | +| simple2 | false | 828 | 828 | declaration | +| simple2 | false | 830 | 830 | declaration | +| simple2 | false | 832 | 832 | return ... | +| simple2 | false | 834 | 834 | { ... } | +| simple2 | false | 836 | 836 | one | +| simple2 | false | 838 | 838 | call to one.~NonTrivial | +| simple2 | false | 839 | 839 | two | +| simple2 | false | 840 | 840 | call to two.~NonTrivial | +| simple2 | true | 828 | 830 | | +| simple2 | true | 830 | 832 | | +| simple2 | true | 832 | 839 | | +| simple2 | true | 834 | 828 | | +| simple2 | true | 836 | 838 | | +| simple2 | true | 838 | 823 | | +| simple2 | true | 839 | 840 | | +| simple2 | true | 840 | 836 | | +| switch_decl_bind | false | 308 | 308 | switch_decl_bind | +| switch_decl_bind | false | 317 | 317 | call to operator int | +| switch_decl_bind | false | 319 | 319 | call to BoxedInt | +| switch_decl_bind | false | 321 | 321 | x | +| switch_decl_bind | false | 323 | 323 | initializer for bi | +| switch_decl_bind | false | 326 | 326 | bi | +| switch_decl_bind | false | 328 | 328 | (condition decl) | +| switch_decl_bind | false | 332 | 332 | 0 | +| switch_decl_bind | false | 333 | 333 | case ...: | +| switch_decl_bind | false | 336 | 336 | bi | +| switch_decl_bind | false | 339 | 339 | m_ptr | +| switch_decl_bind | false | 341 | 341 | * ... | +| switch_decl_bind | false | 343 | 343 | -- ... | +| switch_decl_bind | false | 345 | 345 | ExprStmt | +| switch_decl_bind | false | 347 | 347 | break; | +| switch_decl_bind | false | 351 | 351 | 1 | +| switch_decl_bind | false | 352 | 352 | case ...: | +| switch_decl_bind | false | 354 | 354 | bi | +| switch_decl_bind | false | 356 | 356 | m_ptr | +| switch_decl_bind | false | 358 | 358 | * ... | +| switch_decl_bind | false | 360 | 360 | ++ ... | +| switch_decl_bind | false | 362 | 362 | ExprStmt | +| switch_decl_bind | false | 364 | 364 | break; | +| switch_decl_bind | false | 366 | 366 | default: | +| switch_decl_bind | false | 368 | 368 | bi | +| switch_decl_bind | false | 370 | 370 | m_ptr | +| switch_decl_bind | false | 372 | 372 | * ... | +| switch_decl_bind | false | 376 | 376 | 2 | +| switch_decl_bind | false | 377 | 377 | ... /= ... | +| switch_decl_bind | false | 379 | 379 | ExprStmt | +| switch_decl_bind | false | 381 | 381 | { ... } | +| switch_decl_bind | false | 383 | 383 | switch (...) ... | +| switch_decl_bind | false | 385 | 385 | label ...: | +| switch_decl_bind | false | 387 | 387 | x | +| switch_decl_bind | false | 391 | 391 | 1 | +| switch_decl_bind | false | 392 | 392 | ... = ... | +| switch_decl_bind | false | 394 | 394 | ExprStmt | +| switch_decl_bind | false | 396 | 396 | return ... | +| switch_decl_bind | false | 398 | 398 | { ... } | +| switch_decl_bind | false | 400 | 400 | bi | +| switch_decl_bind | false | 402 | 402 | call to bi.~BoxedInt | +| switch_decl_bind | false | 403 | 403 | bi | +| switch_decl_bind | false | 404 | 404 | call to bi.~BoxedInt | +| switch_decl_bind | false | 405 | 405 | bi | +| switch_decl_bind | false | 406 | 406 | call to bi.~BoxedInt | +| switch_decl_bind | true | 319 | 328 | | +| switch_decl_bind | true | 321 | 319 | | +| switch_decl_bind | true | 328 | 381 | | +| switch_decl_bind | true | 333 | 345 | | +| switch_decl_bind | true | 336 | 339 | | +| switch_decl_bind | true | 339 | 341 | | +| switch_decl_bind | true | 341 | 343 | | +| switch_decl_bind | true | 343 | 347 | | +| switch_decl_bind | true | 345 | 336 | | +| switch_decl_bind | true | 347 | 405 | | +| switch_decl_bind | true | 352 | 362 | | +| switch_decl_bind | true | 354 | 356 | | +| switch_decl_bind | true | 356 | 358 | | +| switch_decl_bind | true | 358 | 360 | | +| switch_decl_bind | true | 360 | 364 | | +| switch_decl_bind | true | 362 | 354 | | +| switch_decl_bind | true | 364 | 403 | | +| switch_decl_bind | true | 366 | 379 | | +| switch_decl_bind | true | 368 | 370 | | +| switch_decl_bind | true | 370 | 372 | | +| switch_decl_bind | true | 372 | 376 | | +| switch_decl_bind | true | 376 | 377 | | +| switch_decl_bind | true | 377 | 400 | | +| switch_decl_bind | true | 379 | 368 | | +| switch_decl_bind | true | 381 | 333 | | +| switch_decl_bind | true | 381 | 352 | | +| switch_decl_bind | true | 381 | 366 | | +| switch_decl_bind | true | 383 | 321 | | +| switch_decl_bind | true | 385 | 394 | | +| switch_decl_bind | true | 387 | 392 | | +| switch_decl_bind | true | 391 | 387 | | +| switch_decl_bind | true | 392 | 396 | | +| switch_decl_bind | true | 394 | 391 | | +| switch_decl_bind | true | 396 | 308 | | +| switch_decl_bind | true | 398 | 383 | | +| switch_decl_bind | true | 400 | 402 | | +| switch_decl_bind | true | 402 | 385 | | +| switch_decl_bind | true | 403 | 404 | | +| switch_decl_bind | true | 404 | 385 | | +| switch_decl_bind | true | 405 | 406 | | +| switch_decl_bind | true | 406 | 385 | | +| while_decl_bind | false | 260 | 260 | while_decl_bind | +| while_decl_bind | false | 269 | 269 | call to operator int | +| while_decl_bind | false | 271 | 271 | call to BoxedInt | +| while_decl_bind | false | 273 | 273 | x | +| while_decl_bind | false | 275 | 275 | initializer for bi | +| while_decl_bind | false | 278 | 278 | bi | +| while_decl_bind | false | 280 | 280 | (bool)... | +| while_decl_bind | false | 281 | 281 | (condition decl) | +| while_decl_bind | false | 283 | 283 | x | +| while_decl_bind | false | 285 | 285 | -- ... | +| while_decl_bind | false | 287 | 287 | ExprStmt | +| while_decl_bind | false | 289 | 289 | { ... } | +| while_decl_bind | false | 291 | 291 | while (...) ... | +| while_decl_bind | false | 293 | 293 | x | +| while_decl_bind | false | 295 | 295 | ++ ... | +| while_decl_bind | false | 297 | 297 | ExprStmt | +| while_decl_bind | false | 299 | 299 | return ... | +| while_decl_bind | false | 301 | 301 | { ... } | +| while_decl_bind | false | 303 | 303 | bi | +| while_decl_bind | false | 305 | 305 | call to bi.~BoxedInt | +| while_decl_bind | false | 306 | 306 | bi | +| while_decl_bind | false | 307 | 307 | call to bi.~BoxedInt | +| while_decl_bind | true | 271 | 281 | | +| while_decl_bind | true | 273 | 271 | | +| while_decl_bind | true | 281 | 289 | T | +| while_decl_bind | true | 281 | 303 | F | +| while_decl_bind | true | 283 | 285 | | +| while_decl_bind | true | 285 | 306 | | +| while_decl_bind | true | 287 | 283 | | +| while_decl_bind | true | 289 | 287 | | +| while_decl_bind | true | 291 | 273 | | +| while_decl_bind | true | 293 | 295 | | +| while_decl_bind | true | 295 | 299 | | +| while_decl_bind | true | 297 | 293 | | +| while_decl_bind | true | 299 | 260 | | +| while_decl_bind | true | 301 | 291 | | +| while_decl_bind | true | 303 | 305 | | +| while_decl_bind | true | 305 | 297 | | +| while_decl_bind | true | 306 | 307 | | +| while_decl_bind | true | 307 | 273 | | diff --git a/csharp/ql/src/.project b/csharp/ql/src/.project index 949b5db1a7c9..1322b1b339e7 100644 --- a/csharp/ql/src/.project +++ b/csharp/ql/src/.project @@ -5,19 +5,8 @@ - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - org.eclipse.pde.PluginNature com.semmle.plugin.qdt.core.qlnature diff --git a/csharp/ql/src/META-INF/MANIFEST.MF b/csharp/ql/src/META-INF/MANIFEST.MF deleted file mode 100644 index 9ee778191ae9..000000000000 --- a/csharp/ql/src/META-INF/MANIFEST.MF +++ /dev/null @@ -1,8 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: Semmle C# Default Queries -Bundle-SymbolicName: com.semmle.plugin.semmlecode.csharp.queries;singleton:=true -Bundle-Version: 1.18.3.qualifier -Bundle-Vendor: Semmle Ltd. -Bundle-ActivationPolicy: lazy -Require-Bundle: com.semmle.plugin.qdt.ui;bundle-version="[1.18.3.qualifier,1.18.3.qualifier]" diff --git a/csharp/ql/src/Security Features/CWE-134/UncontrolledFormatString.qhelp b/csharp/ql/src/Security Features/CWE-134/UncontrolledFormatString.qhelp new file mode 100644 index 000000000000..cc40358f2dba --- /dev/null +++ b/csharp/ql/src/Security Features/CWE-134/UncontrolledFormatString.qhelp @@ -0,0 +1,44 @@ + + + +

+Passing untrusted format strings to String.Format can throw exceptions +and cause a denial of service. For example, if the format string references a missing argument, +or an argument of the wrong type, then System.FormatException is thrown. +

+ +
+ + +

Use a string literal for the format string to prevent the possibility of data flow from +an untrusted source. This also helps to prevent errors where the arguments to +String.Format do not match the format string.

+ +

If the format string cannot be constant, ensure that it comes from a secure +data source or is compiled into the source code.

+ +
+ + +

In this example, the format string is read from an HTTP request, which could cause +the application to crash.

+ + + +
+ + +
+ +
  • +Microsoft docs: +String.Format Method +
  • + + + diff --git a/csharp/ql/src/Security Features/CWE-134/UncontrolledFormatString.ql b/csharp/ql/src/Security Features/CWE-134/UncontrolledFormatString.ql new file mode 100644 index 000000000000..dbebe8fa8ef6 --- /dev/null +++ b/csharp/ql/src/Security Features/CWE-134/UncontrolledFormatString.ql @@ -0,0 +1,34 @@ +/** + * @name Uncontrolled format string + * @description Passing untrusted format strings from remote data sources can throw exceptions + * and cause a denial of service. + * @kind path-problem + * @problem.severity error + * @precision high + * @id cs/uncontrolled-format-string + * @tags security + * external/cwe/cwe-134 + */ + +import csharp +import semmle.code.csharp.dataflow.flowsources.Remote +import semmle.code.csharp.dataflow.TaintTracking +import semmle.code.csharp.frameworks.Format +import DataFlow::PathGraph + +class FormatStringConfiguration extends TaintTracking::Configuration { + FormatStringConfiguration() { this = "FormatStringConfiguration" } + + override predicate isSource(DataFlow::Node source) { + source instanceof RemoteFlowSource + } + + override predicate isSink(DataFlow::Node sink) { + sink.asExpr() = any(FormatCall call).getFormatExpr() + } +} + +from FormatStringConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink +where config.hasFlowPath(source, sink) +select sink.getNode(), source, sink, + "$@ flows to here and is used as a format string.", source.getNode(), source.getNode().toString() diff --git a/csharp/ql/src/Security Features/CWE-134/UncontrolledFormatStringBad.cs b/csharp/ql/src/Security Features/CWE-134/UncontrolledFormatStringBad.cs new file mode 100644 index 000000000000..720354e9a032 --- /dev/null +++ b/csharp/ql/src/Security Features/CWE-134/UncontrolledFormatStringBad.cs @@ -0,0 +1,14 @@ +using System.Web; + +public class HttpHandler : IHttpHandler +{ + string Surname, Forenames, FormattedName; + + public void ProcessRequest(HttpContext ctx) + { + string format = ctx.Request.QueryString["nameformat"]; + + // BAD: Uncontrolled format string. + FormattedName = string.Format(format, Surname, Forenames); + } +} diff --git a/csharp/ql/src/plugin.xml b/csharp/ql/src/plugin.xml deleted file mode 100644 index 532f6c121ad7..000000000000 --- a/csharp/ql/src/plugin.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.cs b/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.cs new file mode 100644 index 000000000000..2edeb91f3571 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.cs @@ -0,0 +1,25 @@ +// semmle-extractor-options: /r:System.Runtime.Extensions.dll /r:System.Collections.Specialized.dll ${testdir}/../../../resources/stubs/System.Web.cs + +using System; +using System.IO; +using System.Web; + +public class TaintedPathHandler : IHttpHandler +{ + public void ProcessRequest(HttpContext ctx) + { + String path = ctx.Request.QueryString["page"]; + + // BAD: Uncontrolled format string. + String.Format(path, "Do not do this"); + + // BAD: Using an IFormatProvider. + String.Format((IFormatProvider)null, path, "Do not do this"); + + // GOOD: Not the format string. + String.Format("Do not do this", path); + + // GOOD: Not the format string. + String.Format((IFormatProvider)null, "Do not do this", path); + } +} diff --git a/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.expected b/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.expected new file mode 100644 index 000000000000..fb4260c8275d --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.expected @@ -0,0 +1,16 @@ +edges +| UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | UncontrolledFormatString.cs:14:23:14:26 | access to local variable path | +| UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | UncontrolledFormatString.cs:17:46:17:49 | access to local variable path | +| UncontrolledFormatStringBad.cs:9:25:9:47 | access to property QueryString | UncontrolledFormatStringBad.cs:12:39:12:44 | access to local variable format | +nodes +| UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | +| UncontrolledFormatString.cs:14:23:14:26 | access to local variable path | +| UncontrolledFormatString.cs:17:46:17:49 | access to local variable path | +| UncontrolledFormatString.cs:20:23:20:38 | "Do not do this" | +| UncontrolledFormatString.cs:23:46:23:61 | "Do not do this" | +| UncontrolledFormatStringBad.cs:9:25:9:47 | access to property QueryString | +| UncontrolledFormatStringBad.cs:12:39:12:44 | access to local variable format | +#select +| UncontrolledFormatString.cs:14:23:14:26 | access to local variable path | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | UncontrolledFormatString.cs:14:23:14:26 | access to local variable path | $@ flows to here and is used as a format string. | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | access to property QueryString | +| UncontrolledFormatString.cs:17:46:17:49 | access to local variable path | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | UncontrolledFormatString.cs:17:46:17:49 | access to local variable path | $@ flows to here and is used as a format string. | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | access to property QueryString | +| UncontrolledFormatStringBad.cs:12:39:12:44 | access to local variable format | UncontrolledFormatStringBad.cs:9:25:9:47 | access to property QueryString | UncontrolledFormatStringBad.cs:12:39:12:44 | access to local variable format | $@ flows to here and is used as a format string. | UncontrolledFormatStringBad.cs:9:25:9:47 | access to property QueryString | access to property QueryString | diff --git a/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.qlref b/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.qlref new file mode 100644 index 000000000000..4b10fc1cb764 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.qlref @@ -0,0 +1 @@ +Security Features/CWE-134/UncontrolledFormatString.ql \ No newline at end of file diff --git a/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatStringBad.cs b/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatStringBad.cs new file mode 100644 index 000000000000..720354e9a032 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatStringBad.cs @@ -0,0 +1,14 @@ +using System.Web; + +public class HttpHandler : IHttpHandler +{ + string Surname, Forenames, FormattedName; + + public void ProcessRequest(HttpContext ctx) + { + string format = ctx.Request.QueryString["nameformat"]; + + // BAD: Uncontrolled format string. + FormattedName = string.Format(format, Surname, Forenames); + } +} diff --git a/java/ql/src/META-INF/MANIFEST.MF b/java/ql/src/META-INF/MANIFEST.MF deleted file mode 100644 index cb4864fe9d76..000000000000 --- a/java/ql/src/META-INF/MANIFEST.MF +++ /dev/null @@ -1,9 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: Semmle Default Java Queries -Bundle-SymbolicName: com.semmle.plugin.semmlecode.queries;singleton:=true -Bundle-Version: 1.18.3.qualifier -Bundle-Vendor: Semmle Ltd. -Bundle-ActivationPolicy: lazy -Require-Bundle: com.semmle.plugin.qdt.ui;bundle-version="[1.18.3.qualifier,1.18.3.qualifier]" - diff --git a/java/ql/src/config/semmlecode.dbscheme b/java/ql/src/config/semmlecode.dbscheme index 87750e19e59e..38ae99eeab76 100755 --- a/java/ql/src/config/semmlecode.dbscheme +++ b/java/ql/src/config/semmlecode.dbscheme @@ -818,4 +818,29 @@ xmllocations( @xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; +/* + * configuration files with key value pairs + */ + +configs( + unique int id: @config +); + +configNames( + unique int id: @configName, + int config: @config ref, + string name: string ref +); + +configValues( + unique int id: @configValue, + int config: @config ref, + string value: string ref +); + +configLocations( + int locatable: @configLocatable ref, + int location: @location_default ref +); +@configLocatable = @config | @configName | @configValue; diff --git a/java/ql/src/config/semmlecode.dbscheme.stats b/java/ql/src/config/semmlecode.dbscheme.stats index 3f7eacf917fb..345b78920aa1 100644 --- a/java/ql/src/config/semmlecode.dbscheme.stats +++ b/java/ql/src/config/semmlecode.dbscheme.stats @@ -535,6 +535,18 @@ @xmlcharacters 439958 + +@config +69795 + + +@configName +69794 + + +@configValue +69691 + externalData @@ -23437,5 +23449,376 @@
  • +OWASP: +Format string attack. +
  • + +configs +69795 + + +id +69795 + + + + + +configNames +69794 + + +id +69794 + + +config +69794 + + +name +12859 + + + + +id +config + + +12 + + +1 +2 +69794 + + + + + + +id +name + + +12 + + +1 +2 +69794 + + + + + + +config +id + + +12 + + +1 +2 +69794 + + + + + + +config +name + + +12 + + +1 +2 +69794 + + + + + + +name +id + + +12 + + +1 +2 +4858 + + +2 +3 +593 + + +3 +4 +2806 + + +4 +10 +169 + + +10 +11 +1900 + + +11 +12 +1757 + + +12 +111 +776 + + + + + + +name +config + + +12 + + +1 +2 +4858 + + +2 +3 +593 + + +3 +4 +2806 + + +4 +10 +169 + + +10 +11 +1900 + + +11 +12 +1757 + + +12 +111 +776 + + + + + + + + +configValues +69691 + + +id +69691 + + +config +69691 + + +value +54399 + + + + +id +config + + +12 + + +1 +2 +69691 + + + + + + +id +value + + +12 + + +1 +2 +69691 + + + + + + +config +id + + +12 + + +1 +2 +69691 + + + + + + +config +value + + +12 + + +1 +2 +69691 + + + + + + +value +id + + +12 + + +1 +2 +48220 + + +2 +4 +4804 + + +4 +546 +1375 + + + + + + +value +config + + +12 + + +1 +2 +48220 + + +2 +4 +4804 + + +4 +546 +1375 + + + + + + + + +configLocations +209280 + + +locatable +209280 + + +location +209280 + + + + +locatable +location + + +12 + + +1 +2 +209280 + + + + + + +location +locatable + + +12 + + +1 +2 +209280 + + + + + + +
    diff --git a/java/ql/src/definitions.ql b/java/ql/src/definitions.ql index 847d6f155d71..d02a8d931b94 100644 --- a/java/ql/src/definitions.ql +++ b/java/ql/src/definitions.ql @@ -158,7 +158,9 @@ class LocationOverridingImportStaticTypeMember extends ImportStaticTypeMember { } Element definition(Element e, string kind) { - e.(MethodAccess).getMethod().getSourceDeclaration() = result and kind = "M" + e.(MethodAccess).getMethod().getSourceDeclaration() = result and + kind = "M" and + not result instanceof InitializerMethod or e.(TypeAccess).getType().(RefType).getSourceDeclaration() = result and kind = "T" or diff --git a/java/ql/src/plugin.xml b/java/ql/src/plugin.xml deleted file mode 100644 index e129a194b56f..000000000000 --- a/java/ql/src/plugin.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/java/ql/test/library-tests/dataflow/fields/C.java b/java/ql/test/library-tests/dataflow/fields/C.java new file mode 100644 index 000000000000..19c702568a02 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/fields/C.java @@ -0,0 +1,27 @@ +public class C { + + private Elem s1 = new Elem(); + private final Elem s2 = new Elem(); + private Elem s3; + private static Elem s4 = new Elem(); + + public static void main(String[] args){ + C c = new C(); + c.func(); + } + + private C() { + this.s3 = new Elem(); + } + + public void func(){ + sink(s1); + sink(s2); + sink(s3); + sink(s4); + } + + public static void sink(Object o) { } + + public static class Elem { } +} diff --git a/java/ql/test/library-tests/dataflow/fields/flow.expected b/java/ql/test/library-tests/dataflow/fields/flow.expected index 49fa14cfd608..15c5142440c4 100644 --- a/java/ql/test/library-tests/dataflow/fields/flow.expected +++ b/java/ql/test/library-tests/dataflow/fields/flow.expected @@ -15,3 +15,7 @@ | B.java:4:14:4:23 | new Elem(...) | B.java:8:10:8:22 | b2.box1.elem2 | | B.java:12:14:12:23 | new Elem(...) | B.java:15:10:15:22 | b2.box1.elem1 | | B.java:12:14:12:23 | new Elem(...) | B.java:16:10:16:22 | b2.box1.elem2 | +| C.java:3:21:3:30 | new Elem(...) | C.java:18:10:18:11 | s1 | +| C.java:4:27:4:36 | new Elem(...) | C.java:19:10:19:11 | s2 | +| C.java:6:28:6:37 | new Elem(...) | C.java:21:10:21:11 | s4 | +| C.java:14:15:14:24 | new Elem(...) | C.java:20:10:20:11 | s3 | diff --git a/java/ql/test/library-tests/structure/EnclosingCallables.expected b/java/ql/test/library-tests/structure/EnclosingCallables.expected index 2655bfc1888d..e6f9c4a6598c 100644 --- a/java/ql/test/library-tests/structure/EnclosingCallables.expected +++ b/java/ql/test/library-tests/structure/EnclosingCallables.expected @@ -6,6 +6,7 @@ | structure/A.java:15:17:15:17 | A | --none-- | | structure/A.java:16:2:16:4 | int | --none-- | | structure/A.java:21:2:21:4 | int | --none-- | +| structure/A.java:24:7:24:7 | (...) | D | | structure/A.java:24:17:24:17 | B | --none-- | | structure/A.java:25:4:25:13 | new (...) | | | structure/A.java:25:8:25:8 | C | | diff --git a/java/ql/test/library-tests/structure/EnclosingStatements.expected b/java/ql/test/library-tests/structure/EnclosingStatements.expected index 45347b3f083e..44cd941e34a7 100644 --- a/java/ql/test/library-tests/structure/EnclosingStatements.expected +++ b/java/ql/test/library-tests/structure/EnclosingStatements.expected @@ -6,6 +6,7 @@ | structure/A.java:15:17:15:17 | A | --none-- | | structure/A.java:16:2:16:4 | int | --none-- | | structure/A.java:21:2:21:4 | int | --none-- | +| structure/A.java:24:7:24:7 | (...) | stmt on line 24 | | structure/A.java:24:17:24:17 | B | --none-- | | structure/A.java:25:4:25:13 | new (...) | stmt on line 25 | | structure/A.java:25:8:25:8 | C | stmt on line 25 | diff --git a/java/ql/test/library-tests/successors/TestLoopBranch/TestSucc.expected b/java/ql/test/library-tests/successors/TestLoopBranch/TestSucc.expected index 384599c71c02..d487429aff1d 100644 --- a/java/ql/test/library-tests/successors/TestLoopBranch/TestSucc.expected +++ b/java/ql/test/library-tests/successors/TestLoopBranch/TestSucc.expected @@ -229,7 +229,9 @@ | TestLoopBranch.java:105:7:105:11 | ... + ... | TestLoopBranch.java:105:3:105:11 | ...=... | | TestLoopBranch.java:105:11:105:11 | y | TestLoopBranch.java:105:7:105:11 | ... + ... | | TestLoopBranch.java:106:3:106:9 | stmt | TestLoopBranch.java:7:14:7:14 | f | -| TestLoopBranch.java:109:9:109:22 | super(...) | TestLoopBranch.java:111:3:111:10 | stmt | +| TestLoopBranch.java:109:9:109:22 | (...) | TestLoopBranch.java:111:3:111:10 | stmt | +| TestLoopBranch.java:109:9:109:22 | stmt | TestLoopBranch.java:109:9:109:22 | (...) | +| TestLoopBranch.java:109:9:109:22 | super(...) | TestLoopBranch.java:109:9:109:22 | stmt | | TestLoopBranch.java:110:2:113:2 | stmt | TestLoopBranch.java:109:9:109:22 | super(...) | | TestLoopBranch.java:111:3:111:9 | ...=... | TestLoopBranch.java:112:3:112:10 | stmt | | TestLoopBranch.java:111:3:111:10 | stmt | TestLoopBranch.java:111:8:111:9 | 33 | @@ -237,7 +239,9 @@ | TestLoopBranch.java:112:3:112:9 | ...=... | TestLoopBranch.java:109:9:109:22 | TestLoopBranch | | TestLoopBranch.java:112:3:112:10 | stmt | TestLoopBranch.java:112:8:112:9 | 44 | | TestLoopBranch.java:112:8:112:9 | 44 | TestLoopBranch.java:112:3:112:9 | ...=... | -| TestLoopBranch.java:115:9:115:22 | super(...) | TestLoopBranch.java:117:3:117:9 | stmt | +| TestLoopBranch.java:115:9:115:22 | (...) | TestLoopBranch.java:117:3:117:9 | stmt | +| TestLoopBranch.java:115:9:115:22 | stmt | TestLoopBranch.java:115:9:115:22 | (...) | +| TestLoopBranch.java:115:9:115:22 | super(...) | TestLoopBranch.java:115:9:115:22 | stmt | | TestLoopBranch.java:116:2:119:2 | stmt | TestLoopBranch.java:115:9:115:22 | super(...) | | TestLoopBranch.java:117:3:117:8 | ...=... | TestLoopBranch.java:118:3:118:9 | stmt | | TestLoopBranch.java:117:3:117:9 | stmt | TestLoopBranch.java:117:8:117:8 | i | diff --git a/java/ql/test/library-tests/successors/TestThrow2/TestSucc.expected b/java/ql/test/library-tests/successors/TestThrow2/TestSucc.expected index e617aa7ffa4c..6f8efd945d6d 100644 --- a/java/ql/test/library-tests/successors/TestThrow2/TestSucc.expected +++ b/java/ql/test/library-tests/successors/TestThrow2/TestSucc.expected @@ -1,6 +1,8 @@ +| TestThrow2.java:3:7:3:16 | (...) | TestThrow2.java:3:7:3:16 | TestThrow2 | +| TestThrow2.java:3:7:3:16 | stmt | TestThrow2.java:3:7:3:16 | (...) | | TestThrow2.java:3:7:3:16 | stmt | TestThrow2.java:3:7:3:16 | super(...) | | TestThrow2.java:3:7:3:16 | stmt | TestThrow2.java:5:2:11:2 | stmt | -| TestThrow2.java:3:7:3:16 | super(...) | TestThrow2.java:3:7:3:16 | TestThrow2 | +| TestThrow2.java:3:7:3:16 | super(...) | TestThrow2.java:3:7:3:16 | stmt | | TestThrow2.java:5:2:11:2 | stmt | TestThrow2.java:6:3:10:3 | stmt | | TestThrow2.java:6:3:10:3 | stmt | TestThrow2.java:6:7:8:3 | stmt | | TestThrow2.java:6:7:8:3 | stmt | TestThrow2.java:7:4:7:13 | stmt | diff --git a/javascript/config/suites/javascript/security b/javascript/config/suites/javascript/security index 2cf8792ddaef..2405b239d9e9 100644 --- a/javascript/config/suites/javascript/security +++ b/javascript/config/suites/javascript/security @@ -9,6 +9,7 @@ + semmlecode-javascript-queries/Security/CWE-079/Xss.ql: /Security/CWE/CWE-079 + semmlecode-javascript-queries/Security/CWE-089/SqlInjection.ql: /Security/CWE/CWE-089 + semmlecode-javascript-queries/Security/CWE-094/CodeInjection.ql: /Security/CWE/CWE-094 ++ semmlecode-javascript-queries/Security/CWE-094/UnsafeDynamicMethodAccess.ql: /Security/CWE/CWE-094 + semmlecode-javascript-queries/Security/CWE-116/IncompleteSanitization.ql: /Security/CWE/CWE-116 + semmlecode-javascript-queries/Security/CWE-116/DoubleEscaping.ql: /Security/CWE/CWE-116 + semmlecode-javascript-queries/Security/CWE-134/TaintedFormatString.ql: /Security/CWE/CWE-134 @@ -28,6 +29,7 @@ + semmlecode-javascript-queries/Security/CWE-640/HostHeaderPoisoningInEmailGeneration.ql: /Security/CWE/CWE-640 + semmlecode-javascript-queries/Security/CWE-643/XpathInjection.ql: /Security/CWE/CWE-643 + semmlecode-javascript-queries/Security/CWE-730/RegExpInjection.ql: /Security/CWE/CWE-730 ++ semmlecode-javascript-queries/Security/CWE-754/UnvalidatedDynamicMethodCall.ql: /Security/CWE/CWE-754 + semmlecode-javascript-queries/Security/CWE-770/MissingRateLimiting.ql: /Security/CWE/CWE-770 + semmlecode-javascript-queries/Security/CWE-776/XmlBomb.ql: /Security/CWE/CWE-776 + semmlecode-javascript-queries/Security/CWE-798/HardcodedCredentials.ql: /Security/CWE/CWE-798 diff --git a/javascript/extractor/src/com/semmle/jcorn/CustomParser.java b/javascript/extractor/src/com/semmle/jcorn/CustomParser.java index 73aa3bad0b4a..193642d92471 100644 --- a/javascript/extractor/src/com/semmle/jcorn/CustomParser.java +++ b/javascript/extractor/src/com/semmle/jcorn/CustomParser.java @@ -153,7 +153,7 @@ protected Expression parseExprAtom(DestructuringErrors refDestructuringErrors) { Identifier name = this.parseIdent(true); this.expect(TokenType.parenL); List args = this.parseExprList(TokenType.parenR, false, false, null); - CallExpression node = new CallExpression(new SourceLocation(startLoc), name, new ArrayList<>(), args); + CallExpression node = new CallExpression(new SourceLocation(startLoc), name, new ArrayList<>(), args, false, false); return this.finishNode(node); } else { return super.parseExprAtom(refDestructuringErrors); @@ -212,7 +212,7 @@ protected INode parseFunction(Position startLoc, boolean isStatement, boolean al * A.f = function f(...) { ... }; */ SourceLocation memloc = new SourceLocation(iface.getName() + "::" + id.getName(), iface.getLoc().getStart(), id.getLoc().getEnd()); - MemberExpression mem = new MemberExpression(memloc, iface, new Identifier(id.getLoc(), id.getName()), false); + MemberExpression mem = new MemberExpression(memloc, iface, new Identifier(id.getLoc(), id.getName()), false, false, false); AssignmentExpression assgn = new AssignmentExpression(result.getLoc(), "=", mem, ((FunctionDeclaration)result).asFunctionExpression()); return new ExpressionStatement(result.getLoc(), assgn); } diff --git a/javascript/extractor/src/com/semmle/jcorn/Parser.java b/javascript/extractor/src/com/semmle/jcorn/Parser.java index a8b964e330d1..5aa897141613 100644 --- a/javascript/extractor/src/com/semmle/jcorn/Parser.java +++ b/javascript/extractor/src/com/semmle/jcorn/Parser.java @@ -29,6 +29,7 @@ import com.semmle.js.ast.BreakStatement; import com.semmle.js.ast.CallExpression; import com.semmle.js.ast.CatchClause; +import com.semmle.js.ast.Chainable; import com.semmle.js.ast.ClassBody; import com.semmle.js.ast.ClassDeclaration; import com.semmle.js.ast.ClassExpression; @@ -504,6 +505,14 @@ private Token readToken_dot() { } } + private Token readToken_question() { // '?' + int next = charAt(this.pos + 1); + int next2 = charAt(this.pos + 2); + if (this.options.esnext() && next == '.' && !('0' <= next2 && next2 <= '9')) // '?.', but not '?.X' where X is a digit + return this.finishOp(TokenType.questiondot, 2); + return this.finishOp(TokenType.question, 1); + } + private Token readToken_slash() { // '/' int next = charAt(this.pos + 1); if (this.exprAllowed) { @@ -616,7 +625,7 @@ protected Token getTokenFromCode(int code) { case 123: ++this.pos; return this.finishToken(TokenType.braceL); case 125: ++this.pos; return this.finishToken(TokenType.braceR); case 58: ++this.pos; return this.finishToken(TokenType.colon); - case 63: ++this.pos; return this.finishToken(TokenType.question); + case 63: return this.readToken_question(); case 96: // '`' if (this.options.ecmaVersion() < 6) break; @@ -1465,17 +1474,19 @@ protected Expression parseSubscripts(Expression base, int startPos, Position sta } } + private boolean isOnOptionalChain(boolean optional, Expression base) { + return optional || base instanceof Chainable && ((Chainable)base).isOnOptionalChain(); + } + /** * Parse a single subscript {@code s}; if more subscripts could follow, return {@code Pair.make(s, true}, * otherwise return {@code Pair.make(s, false)}. */ protected Pair parseSubscript(final Expression base, Position startLoc, boolean noCalls) { boolean maybeAsyncArrow = this.options.ecmaVersion() >= 8 && base instanceof Identifier && "async".equals(((Identifier) base).getName()) && !this.canInsertSemicolon(); - if (this.eat(TokenType.dot)) { - MemberExpression node = new MemberExpression(new SourceLocation(startLoc), base, this.parseIdent(true), false); - return Pair.make(this.finishNode(node), true); - } else if (this.eat(TokenType.bracketL)) { - MemberExpression node = new MemberExpression(new SourceLocation(startLoc), base, this.parseExpression(false, null), true); + boolean optional = this.eat(TokenType.questiondot); + if (this.eat(TokenType.bracketL)) { + MemberExpression node = new MemberExpression(new SourceLocation(startLoc), base, this.parseExpression(false, null), true, optional, isOnOptionalChain(optional, base)); this.expect(TokenType.bracketR); return Pair.make(this.finishNode(node), true); } else if (!noCalls && this.eat(TokenType.parenL)) { @@ -1494,11 +1505,17 @@ protected Pair parseSubscript(final Expression base, Positi this.checkExpressionErrors(refDestructuringErrors, true); if (oldYieldPos > 0) this.yieldPos = oldYieldPos; if (oldAwaitPos > 0) this.awaitPos = oldAwaitPos; - CallExpression node = new CallExpression(new SourceLocation(startLoc), base, new ArrayList<>(), exprList); + CallExpression node = new CallExpression(new SourceLocation(startLoc), base, new ArrayList<>(), exprList, optional, isOnOptionalChain(optional, base)); return Pair.make(this.finishNode(node), true); } else if (this.type == TokenType.backQuote) { + if (isOnOptionalChain(optional, base)) { + this.raise(base, "An optional chain may not be used in a tagged template expression."); + } TaggedTemplateExpression node = new TaggedTemplateExpression(new SourceLocation(startLoc), base, this.parseTemplate(true)); return Pair.make(this.finishNode(node), true); + } else if (optional || this.eat(TokenType.dot)) { + MemberExpression node = new MemberExpression(new SourceLocation(startLoc), base, this.parseIdent(true), false, optional, isOnOptionalChain(optional, base)); + return Pair.make(this.finishNode(node), true); } else { return Pair.make(base, false); } @@ -1719,6 +1736,10 @@ protected Expression parseNew() { int innerStartPos = this.start; Position innerStartLoc = this.startLoc; Expression callee = this.parseSubscripts(this.parseExprAtom(null), innerStartPos, innerStartLoc, true); + + if (isOnOptionalChain(false, callee)) + this.raise(callee, "An optional chain may not be used in a `new` expression."); + List arguments; if (this.eat(TokenType.parenL)) arguments = this.parseExprList(TokenType.parenR, this.options.ecmaVersion() >= 8, false, null); @@ -2159,9 +2180,12 @@ protected INode toAssignable(INode node, boolean isBinding) { return new ParenthesizedExpression(node.getLoc(), (Expression) this.toAssignable(expr, isBinding)); } - if (node instanceof MemberExpression) + if (node instanceof MemberExpression) { + if (isOnOptionalChain(false, (MemberExpression)node)) + this.raise(node, "Invalid left-hand side in assignment"); if (!isBinding) return node; + } this.raise(node, "Assigning to rvalue"); } @@ -3016,6 +3040,10 @@ protected MemberDefinition parseClassMember(boolean hadConstructor) { return parseClassPropertyBody(pi, hadConstructor, isStatic); } + protected boolean atGetterSetterName(PropertyInfo pi) { + return !pi.isGenerator && !pi.isAsync && pi.key instanceof Identifier && this.type != TokenType.parenL && (((Identifier) pi.key).getName().equals("get") || ((Identifier) pi.key).getName().equals("set")); + } + /** * Parse a method declaration in a class, assuming that its name has already been consumed. */ @@ -3023,7 +3051,7 @@ protected MemberDefinition parseClassPropertyBody(PropertyInfo pi, boolean ha pi.kind = "method"; boolean isGetSet = false; if (!pi.computed) { - if (!pi.isGenerator && !pi.isAsync && pi.key instanceof Identifier && this.type != TokenType.parenL && (((Identifier) pi.key).getName().equals("get") || ((Identifier) pi.key).getName().equals("set"))) { + if (atGetterSetterName(pi)) { isGetSet = true; pi.kind = ((Identifier) pi.key).getName(); this.parsePropertyName(pi); diff --git a/javascript/extractor/src/com/semmle/jcorn/TokenType.java b/javascript/extractor/src/com/semmle/jcorn/TokenType.java index 3cced9b72f5a..42cbacdf69c7 100644 --- a/javascript/extractor/src/com/semmle/jcorn/TokenType.java +++ b/javascript/extractor/src/com/semmle/jcorn/TokenType.java @@ -76,6 +76,7 @@ public void updateContext(Parser parser, TokenType prevType) { semi = new TokenType(new Properties(";").beforeExpr()), colon = new TokenType(new Properties(":").beforeExpr()), dot = new TokenType(new Properties(".")), + questiondot = new TokenType(new Properties("?.")), question = new TokenType(new Properties("?").beforeExpr()), arrow = new TokenType(new Properties("=>").beforeExpr()), template = new TokenType(new Properties("template")), diff --git a/javascript/extractor/src/com/semmle/jcorn/flow/FlowParser.java b/javascript/extractor/src/com/semmle/jcorn/flow/FlowParser.java index 1aebb862a61d..7f6b815bd82f 100644 --- a/javascript/extractor/src/com/semmle/jcorn/flow/FlowParser.java +++ b/javascript/extractor/src/com/semmle/jcorn/flow/FlowParser.java @@ -146,6 +146,10 @@ private void flowParseTypeInitialiser(TokenType tok, boolean allowLeadingPipeOrA boolean oldInType = inType; inType = true; this.expect(tok == null ? TokenType.colon : tok); + if (this.type == TokenType.modulo) {// an annotation like '%checks' without a preceeding type + inType = oldInType; + return; + } if (allowLeadingPipeOrAnd) { if (this.type == TokenType.bitwiseAND || this.type == TokenType.bitwiseOR) { this.next(); @@ -223,14 +227,29 @@ private void flowParseDeclareModule(Position start) { while (this.type != TokenType.braceR) { Position stmtStart = startLoc; - // todo: declare check - this.next(); + if (this.eat(TokenType._import)) { + this.flowParseDeclareImport(stmtStart); + } else { + // todo: declare check + this.next(); - this.flowParseDeclare(stmtStart); + this.flowParseDeclare(stmtStart); + } } this.expect(TokenType.braceR); } + private void flowParseDeclareImport(Position stmtStart) { + String kind = flowParseImportSpecifiers(); + if (kind == null) { + this.raise(stmtStart, "Imports within a `declare module` body must always be `import type` or `import typeof`."); + } + this.expect(TokenType.name); + this.expectContextual("from"); + this.expect(TokenType.string); + this.semicolon(); + } + private void flowParseDeclareModuleExports() { this.expectContextual("module"); this.expect(TokenType.dot); @@ -737,7 +756,7 @@ private void flowParsePrimaryType() { private void flowParsePostfixType() { this.flowParsePrimaryType(); - if (this.type == TokenType.bracketL) { + while (this.type == TokenType.bracketL) { this.expect(TokenType.bracketL); this.expect(TokenType.bracketR); } @@ -807,11 +826,20 @@ protected Node parseFunctionBody(Identifier id, List params, boolean // if allowExpression is true then we're parsing an arrow function and if // there's a return type then it's been handled elsewhere this.flowParseTypeAnnotation(); + this.flowParseChecksAnnotation(); } return super.parseFunctionBody(id, params, isArrowFunction); } + private void flowParseChecksAnnotation() { + // predicate functions with the special '%checks' annotation + if (this.type == TokenType.modulo && lookaheadIsIdent("checks", true)) { + this.next(); + this.next(); + } + } + // interfaces @Override protected Statement parseStatement(boolean declaration, boolean topLevel, Set exports) { @@ -975,24 +1003,30 @@ protected Expression processBindingListItem(Expression param) { return param; } + private String flowParseImportSpecifiers() { + String kind = null; + if (this.type == TokenType._typeof) { + kind = "typeof"; + } else if (this.isContextual("type")) { + kind = "type"; + } + if (kind != null) { + String lh = lookahead(4); + if (!lh.isEmpty()) { + int c = lh.codePointAt(0); + if ((Identifiers.isIdentifierStart(c, true) && !"from".equals(lh)) || c == '{' || c == '*') { + this.next(); + } + } + } + return kind; + } + @Override protected List parseImportSpecifiers() { String kind = null; if (flow()) { - if (this.type == TokenType._typeof) { - kind = "typeof"; - } else if (this.isContextual("type")) { - kind = "type"; - } - if (kind != null) { - String lh = lookahead(4); - if (!lh.isEmpty()) { - int c = lh.codePointAt(0); - if ((Identifiers.isIdentifierStart(c, true) && !"from".equals(lh)) || c == '{' || c == '*') { - this.next(); - } - } - } + kind = flowParseImportSpecifiers(); } List specs = super.parseImportSpecifiers(); @@ -1102,6 +1136,7 @@ protected ParenthesisedExpressions parseParenthesisedExpressions(DestructuringEr boolean oldNoAnonFunctionType = noAnonFunctionType; noAnonFunctionType = true; flowParseTypeAnnotation(); + flowParseChecksAnnotation(); noAnonFunctionType = oldNoAnonFunctionType; if (this.type != TokenType.arrow) unexpected(); @@ -1158,4 +1193,12 @@ protected void parsePropertyName(PropertyInfo result) { this.eat(TokenType.plusMin); super.parsePropertyName(result); } + + @Override + protected boolean atGetterSetterName(PropertyInfo pi) { + if (flow() && this.isRelational("<")) + return false; + return super.atGetterSetterName(pi); + } + } diff --git a/javascript/extractor/src/com/semmle/js/ast/AST2JSON.java b/javascript/extractor/src/com/semmle/js/ast/AST2JSON.java index ad084f62f751..1ecd911d9235 100644 --- a/javascript/extractor/src/com/semmle/js/ast/AST2JSON.java +++ b/javascript/extractor/src/com/semmle/js/ast/AST2JSON.java @@ -215,6 +215,7 @@ public JsonElement visit(CallExpression nd, Void c) { JsonObject result = this.mkNode(nd); result.add("callee", visit(nd.getCallee())); result.add("arguments", visit(nd.getArguments())); + result.add("optional", new JsonPrimitive(nd.isOptional())); return result; } @@ -424,6 +425,7 @@ public JsonElement visit(MemberExpression nd, Void c) { result.add("object", visit(nd.getObject())); result.add("property", visit(nd.getProperty())); result.add("computed", new JsonPrimitive(nd.isComputed())); + result.add("optional", new JsonPrimitive(nd.isOptional())); return result; } diff --git a/javascript/extractor/src/com/semmle/js/ast/CallExpression.java b/javascript/extractor/src/com/semmle/js/ast/CallExpression.java index acb9dd1420a8..5ef43d156330 100644 --- a/javascript/extractor/src/com/semmle/js/ast/CallExpression.java +++ b/javascript/extractor/src/com/semmle/js/ast/CallExpression.java @@ -8,8 +8,8 @@ * A function call expression such as f(1, 1). */ public class CallExpression extends InvokeExpression { - public CallExpression(SourceLocation loc, Expression callee, List typeArguments, List arguments) { - super("CallExpression", loc, callee, typeArguments, arguments); + public CallExpression(SourceLocation loc, Expression callee, List typeArguments, List arguments, Boolean optional, Boolean onOptionalChain) { + super("CallExpression", loc, callee, typeArguments, arguments, optional, onOptionalChain); } @Override diff --git a/javascript/extractor/src/com/semmle/js/ast/Chainable.java b/javascript/extractor/src/com/semmle/js/ast/Chainable.java new file mode 100644 index 000000000000..ebb3a37f31df --- /dev/null +++ b/javascript/extractor/src/com/semmle/js/ast/Chainable.java @@ -0,0 +1,16 @@ +package com.semmle.js.ast; + +/** + * A chainable expression, such as a member access or function call. + */ +public interface Chainable { + /** + * Is this step of the chain optional? + */ + abstract boolean isOptional(); + + /** + * Is this on an optional chain? + */ + abstract boolean isOnOptionalChain(); +} diff --git a/javascript/extractor/src/com/semmle/js/ast/InvokeExpression.java b/javascript/extractor/src/com/semmle/js/ast/InvokeExpression.java index e8eebe1bc7f5..9c6f133138de 100644 --- a/javascript/extractor/src/com/semmle/js/ast/InvokeExpression.java +++ b/javascript/extractor/src/com/semmle/js/ast/InvokeExpression.java @@ -8,20 +8,24 @@ /** * An invocation, that is, either a {@link CallExpression} or a {@link NewExpression}. */ -public abstract class InvokeExpression extends Expression implements INodeWithSymbol { +public abstract class InvokeExpression extends Expression implements INodeWithSymbol, Chainable { private final Expression callee; private final List typeArguments; private final List arguments; + private final boolean optional; + private final boolean onOptionalChain; private int resolvedSignatureId = -1; private int overloadIndex = -1; private int symbol = -1; public InvokeExpression(String type, SourceLocation loc, Expression callee, List typeArguments, - List arguments) { + List arguments, Boolean optional, Boolean onOptionalChain) { super(type, loc); this.callee = callee; this.typeArguments = typeArguments; this.arguments = arguments; + this.optional = optional == Boolean.TRUE; + this.onOptionalChain = onOptionalChain == Boolean.TRUE; } /** @@ -45,6 +49,16 @@ public List getArguments() { return arguments; } + @Override + public boolean isOptional() { + return optional; + } + + @Override + public boolean isOnOptionalChain() { + return onOptionalChain; + } + public int getResolvedSignatureId() { return resolvedSignatureId; } @@ -70,4 +84,4 @@ public int getSymbol() { public void setSymbol(int symbol) { this.symbol = symbol; } -} \ No newline at end of file +} diff --git a/javascript/extractor/src/com/semmle/js/ast/MemberExpression.java b/javascript/extractor/src/com/semmle/js/ast/MemberExpression.java index 093e8a5b4c0e..a5c4eb531c3a 100644 --- a/javascript/extractor/src/com/semmle/js/ast/MemberExpression.java +++ b/javascript/extractor/src/com/semmle/js/ast/MemberExpression.java @@ -6,16 +6,20 @@ /** * A member expression, either computed (e[f]) or static (e.f). */ -public class MemberExpression extends Expression implements ITypeExpression, INodeWithSymbol { +public class MemberExpression extends Expression implements ITypeExpression, INodeWithSymbol, Chainable { private final Expression object, property; private final boolean computed; + private final boolean optional; + private final boolean onOptionalChain; private int symbol = -1; - public MemberExpression(SourceLocation loc, Expression object, Expression property, Boolean computed) { + public MemberExpression(SourceLocation loc, Expression object, Expression property, Boolean computed, Boolean optional, Boolean onOptionalChain) { super("MemberExpression", loc); this.object = object; this.property = property; this.computed = computed == Boolean.TRUE; + this.optional = optional == Boolean.TRUE; + this.onOptionalChain = onOptionalChain == Boolean.TRUE; } @Override @@ -45,6 +49,16 @@ public boolean isComputed() { return computed; } + @Override + public boolean isOptional() { + return optional; + } + + @Override + public boolean isOnOptionalChain() { + return onOptionalChain; + } + @Override public int getSymbol() { return symbol; diff --git a/javascript/extractor/src/com/semmle/js/ast/NewExpression.java b/javascript/extractor/src/com/semmle/js/ast/NewExpression.java index eb5ba5d8eb4e..8cd8ad0ea5c5 100644 --- a/javascript/extractor/src/com/semmle/js/ast/NewExpression.java +++ b/javascript/extractor/src/com/semmle/js/ast/NewExpression.java @@ -9,7 +9,7 @@ */ public class NewExpression extends InvokeExpression { public NewExpression(SourceLocation loc, Expression callee, List typeArguments, List arguments) { - super("NewExpression", loc, callee, typeArguments, arguments); + super("NewExpression", loc, callee, typeArguments, arguments, false, false); } @Override diff --git a/javascript/extractor/src/com/semmle/js/ast/NodeCopier.java b/javascript/extractor/src/com/semmle/js/ast/NodeCopier.java index 040b594b49bc..ef826394779c 100644 --- a/javascript/extractor/src/com/semmle/js/ast/NodeCopier.java +++ b/javascript/extractor/src/com/semmle/js/ast/NodeCopier.java @@ -97,7 +97,7 @@ public BlockStatement visit(BlockStatement nd, Void q) { @Override public CallExpression visit(CallExpression nd, Void q) { - return new CallExpression(visit(nd.getLoc()), copy(nd.getCallee()), copy(nd.getTypeArguments()), copy(nd.getArguments())); + return new CallExpression(visit(nd.getLoc()), copy(nd.getCallee()), copy(nd.getTypeArguments()), copy(nd.getArguments()), nd.isOptional(), nd.isOnOptionalChain()); } @Override @@ -140,7 +140,7 @@ public LogicalExpression visit(LogicalExpression nd, Void q) { @Override public MemberExpression visit(MemberExpression nd, Void q) { - return new MemberExpression(visit(nd.getLoc()), copy(nd.getObject()), copy(nd.getProperty()), nd.isComputed()); + return new MemberExpression(visit(nd.getLoc()), copy(nd.getObject()), copy(nd.getProperty()), nd.isComputed(), nd.isOptional(), nd.isOnOptionalChain()); } @Override diff --git a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java index db1cd18d94d0..2e0f20726012 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java @@ -405,6 +405,9 @@ public Label visit(InvokeExpression nd, Context c) { if (nd.getOverloadIndex() != -1) { trapwriter.addTuple("invoke_expr_overload_index", key, nd.getOverloadIndex()); } + if (nd.isOptional()) { + trapwriter.addTuple("isOptionalChaining", key); + } emitNodeSymbol(nd, key); return key; } @@ -531,6 +534,9 @@ public Label visit(MemberExpression nd, Context c) { visit(nd.getObject(), key, 0, baseIdContext); visit(nd.getProperty(), key, 1, nd.isComputed() ? IdContext.varBind : IdContext.label); } + if (nd.isOptional()) { + trapwriter.addTuple("isOptionalChaining", key); + } return key; } @@ -1245,7 +1251,7 @@ private void addDefaultConstructor(AClass ac) { Super superExpr = new Super(fakeLoc("super", loc)); CallExpression superCall = new CallExpression( fakeLoc("super(...args)", loc), - superExpr, new ArrayList<>(), CollectionUtil.makeList(spreadArgs)); + superExpr, new ArrayList<>(), CollectionUtil.makeList(spreadArgs), false, false); ExpressionStatement superCallStmt = new ExpressionStatement( fakeLoc("super(...args);", loc), superCall); body.getBody().add(superCallStmt); diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index 16a0226321cf..68e05e46eb44 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -31,13 +31,13 @@ import com.semmle.ts.extractor.TypeExtractor; import com.semmle.ts.extractor.TypeTable; import com.semmle.util.data.StringUtil; -import com.semmle.util.defect.Language; import com.semmle.util.exception.Exceptions; import com.semmle.util.exception.ResourceError; import com.semmle.util.exception.UserError; import com.semmle.util.extraction.ExtractorOutputConfig; import com.semmle.util.files.FileUtil; import com.semmle.util.io.csv.CSVReader; +import com.semmle.util.language.LegacyLanguage; import com.semmle.util.process.Env; import com.semmle.util.projectstructure.ProjectLayout; import com.semmle.util.trap.TrapWriter; @@ -179,7 +179,7 @@ public class AutoBuild { public AutoBuild() { this.LGTM_SRC = toRealPath(getPathFromEnvVar("LGTM_SRC")); this.SEMMLE_DIST = getPathFromEnvVar(Env.Var.SEMMLE_DIST.toString()); - this.outputConfig = new ExtractorOutputConfig(Language.JAVASCRIPT); + this.outputConfig = new ExtractorOutputConfig(LegacyLanguage.JAVASCRIPT); this.trapCache = mkTrapCache(); this.typeScriptMode = getEnumFromEnvVar("LGTM_INDEX_TYPESCRIPT", TypeScriptMode.class, TypeScriptMode.BASIC); this.defaultEncoding = getEnvVar("LGTM_INDEX_DEFAULT_ENCODING"); diff --git a/javascript/extractor/src/com/semmle/js/extractor/CFGExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/CFGExtractor.java index 27ecbb571e25..858f98fa0ed5 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/CFGExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/CFGExtractor.java @@ -24,6 +24,7 @@ import com.semmle.js.ast.BreakStatement; import com.semmle.js.ast.CallExpression; import com.semmle.js.ast.CatchClause; +import com.semmle.js.ast.Chainable; import com.semmle.js.ast.ClassBody; import com.semmle.js.ast.ClassDeclaration; import com.semmle.js.ast.ClassExpression; @@ -776,6 +777,9 @@ public Finally(SourceLocation loc, BlockStatement body) { // cache the set of normal control flow successors private final Map followingCache = new LinkedHashMap(); + // map from a node in a chain of property accesses or calls to the successor info for the first node in the chain + private final Map chainRootSuccessors = new LinkedHashMap(); + /** * Generate entry node. */ @@ -1637,16 +1641,36 @@ public Void visit(YieldExpression nd, SuccessorInfo i) { return null; } + private void preVisitChainable(Chainable chainable, Expression base, SuccessorInfo i) { + if (!chainable.isOnOptionalChain()) // optimization: bookkeeping is only needed for optional chains + return; + // start of chain + chainRootSuccessors.putIfAbsent(chainable, i); + // next step in chain + if (base instanceof Chainable) + chainRootSuccessors.put((Chainable)base, chainRootSuccessors.get(chainable)); + } + + private void postVisitChainable(Chainable chainable, Expression base, boolean optional) { + if (optional) { + succ(base, chainRootSuccessors.get(chainable).getSuccessors(false)); + } + chainRootSuccessors.remove(chainable); + } + @Override public Void visit(MemberExpression nd, SuccessorInfo i) { + preVisitChainable(nd, nd.getObject(), i); seq(nd.getObject(), nd.getProperty(), nd); // property accesses may throw succ(nd, union(this.findTarget(JumpType.THROW, null), i.getGuardedSuccessors(nd))); + postVisitChainable(nd, nd.getObject(), nd.isOptional()); return null; } @Override public Void visit(InvokeExpression nd, SuccessorInfo i) { + preVisitChainable(nd, nd.getCallee(), i); seq(nd.getCallee(), nd.getArguments(), nd); Object succs = i.getGuardedSuccessors(nd); if (nd instanceof CallExpression && nd.getCallee() instanceof Super && !instanceFields.isEmpty()) { @@ -1660,6 +1684,7 @@ public Void visit(InvokeExpression nd, SuccessorInfo i) { } // calls may throw succ(nd, union(this.findTarget(JumpType.THROW, null), succs)); + postVisitChainable(nd, nd.getCallee(), nd.isOptional()); return null; } diff --git a/javascript/extractor/src/com/semmle/js/extractor/FileExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/FileExtractor.java index 1be577c04aec..1e88d1897cd4 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/FileExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/FileExtractor.java @@ -5,6 +5,7 @@ import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; +import java.nio.charset.Charset; import java.util.LinkedHashSet; import java.util.Set; import java.util.regex.Pattern; @@ -38,6 +39,11 @@ public class FileExtractor { */ public static final Pattern JSON_OBJECT_START = Pattern.compile("^(?s)\\s*\\{\\s*\"([^\"]|\\\\.)*\"\\s*:.*"); + /** + * The charset for decoding UTF-8 strings. + */ + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + /** * Information about supported file types. */ @@ -169,6 +175,11 @@ private boolean hasBadFileHeader(File f, String lcExt, ExtractorConfig config) { if (isXml(bytes, length)) return true; + // Avoid files with an unrecognized shebang header. + if (hasUnrecognizedShebang(bytes, length)) { + return true; + } + return false; } catch (IOException e) { Exceptions.ignore(e, "Let extractor handle this one."); @@ -249,6 +260,38 @@ private boolean hasUnprintableUtf8(byte[] bytes, int length) { return false; } + /** + * Returns true if the byte sequence starts with a shebang line that is not + * recognized as a JavaScript interpreter. + */ + private boolean hasUnrecognizedShebang(byte[] bytes, int length) { + // Shebangs preceded by a BOM aren't recognized in UNIX, but the BOM might only + // be present in the source file, to be stripped out in the build process. + int startIndex = skipBOM(bytes, length); + if (startIndex + 2 >= length) return false; + if (bytes[startIndex] != '#' || bytes[startIndex + 1] != '!') { + return false; + } + int endOfLine = -1; + for (int i = startIndex; i < length; ++i) { + if (bytes[i] == '\r' || bytes[i] == '\n') { + endOfLine = i; + break; + } + } + if (endOfLine == -1) { + // The shebang is either very long or there are no other lines in the file. + // Treat this as unrecognized. + return true; + } + // Extract the shebang text + int startOfText = startIndex + "#!".length(); + int lengthOfText = endOfLine - startOfText; + String text = new String(bytes, startOfText, lengthOfText, UTF8_CHARSET); + // Check if the shebang is a recognized JavaScript intepreter. + return !NODE_INVOCATION.matcher(text).find(); + } + @Override public IExtractor mkExtractor(ExtractorConfig config, ExtractorState state) { return new TypeScriptExtractor(config, state.getTypeScriptParser()); diff --git a/javascript/extractor/src/com/semmle/js/extractor/Main.java b/javascript/extractor/src/com/semmle/js/extractor/Main.java index 892fc270353e..538627ee6f67 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/Main.java +++ b/javascript/extractor/src/com/semmle/js/extractor/Main.java @@ -17,17 +17,17 @@ import com.semmle.js.extractor.trapcache.ITrapCache; import com.semmle.js.parser.ParsedProject; import com.semmle.js.parser.TypeScriptParser; -import com.semmle.ts.extractor.TypeTable; import com.semmle.ts.extractor.TypeExtractor; +import com.semmle.ts.extractor.TypeTable; import com.semmle.util.data.StringUtil; import com.semmle.util.data.UnitParser; -import com.semmle.util.defect.Language; import com.semmle.util.exception.ResourceError; import com.semmle.util.exception.UserError; import com.semmle.util.extraction.ExtractorOutputConfig; import com.semmle.util.files.FileUtil; import com.semmle.util.files.PathMatcher; import com.semmle.util.io.WholeIO; +import com.semmle.util.language.LegacyLanguage; import com.semmle.util.process.ArgsParser; import com.semmle.util.process.ArgsParser.FileMode; import com.semmle.util.trap.TrapWriter; @@ -41,7 +41,7 @@ public class Main { * such a way that it may produce different tuples for the same file under the same * {@link ExtractorConfig}. */ - public static final String EXTRACTOR_VERSION = "2018-11-12"; + public static final String EXTRACTOR_VERSION = "2018-11-22_a"; public static final Pattern NEWLINE = Pattern.compile("\n"); @@ -461,7 +461,7 @@ private File normalizeFile(File root) { public static void main(String[] args) { try { - new Main(new ExtractorOutputConfig(Language.JAVASCRIPT)).run(args); + new Main(new ExtractorOutputConfig(LegacyLanguage.JAVASCRIPT)).run(args); } catch (UserError e) { System.err.println(e.getMessage()); if (!e.reportAsInfoMessage()) diff --git a/javascript/extractor/src/com/semmle/js/extractor/test/TrapTests.java b/javascript/extractor/src/com/semmle/js/extractor/test/TrapTests.java index af1c05bb7760..a0615f231469 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/test/TrapTests.java +++ b/javascript/extractor/src/com/semmle/js/extractor/test/TrapTests.java @@ -4,6 +4,9 @@ import java.io.StringWriter; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map.Entry; @@ -40,7 +43,9 @@ public static Iterable tests() { List testData = new ArrayList(); // iterate over all test groups - for (String testgroup : BASE.list()) { + List testGroups = Arrays.asList(BASE.list()); + testGroups.sort(Comparator.naturalOrder()); + for (String testgroup : testGroups) { File root = new File(BASE, testgroup); if (root.isDirectory()) { // check for options.json file and process it if it exists @@ -78,7 +83,9 @@ public static Iterable tests() { testData.add(new Object[] { testgroup, "tsconfig", new ArrayList(options) }); } else { // create isolated tests for each input file in the group - for (String testfile : inputDir.list()) { + List tests = Arrays.asList(inputDir.list()); + tests.sort(Comparator.naturalOrder()); + for (String testfile : tests) { testData.add(new Object[] { testgroup, testfile, new ArrayList(options) }); } } @@ -149,7 +156,13 @@ public void close() { // convert to and from UTF-8 to mimick treatment of unencodable characters byte[] actual_utf8_bytes = StringUtil.stringToBytes(sw.toString()); String actual = new String(actual_utf8_bytes, Charset.forName("UTF-8")); - String expected = new WholeIO().strictreadText(new File(outputDir, f.getName() + ".trap")); + File trap = new File(outputDir, f.getName() + ".trap"); + boolean replaceExpectedOutput = false; + if (replaceExpectedOutput) { + System.out.println("Replacing expected output for " + trap); + new WholeIO().strictwrite(trap, actual); + } + String expected = new WholeIO().strictreadText(trap); expectedVsActual.add(Pair.make(expected, actual)); } }; diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java index 862dfd33663e..c462686ea901 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java @@ -860,7 +860,7 @@ private Node convertCallExpression(JsonObject node, SourceLocation loc) throws P } Expression callee = convertChild(node, "expression"); List typeArguments = convertChildrenAsTypes(node, "typeArguments"); - CallExpression call = new CallExpression(loc, callee, typeArguments, arguments); + CallExpression call = new CallExpression(loc, callee, typeArguments, arguments, false, false); attachResolvedSignature(call, node); return call; } @@ -1057,7 +1057,7 @@ private Node convertElementAccessExpression(JsonObject node, SourceLocation loc) throws ParseError { Expression object = convertChild(node, "expression"); Expression property = convertChild(node, "argumentExpression"); - return new MemberExpression(loc, object, property, true); + return new MemberExpression(loc, object, property, true, false, false); } private Node convertEmptyStatement(SourceLocation loc) { @@ -1341,7 +1341,7 @@ private ITypeExpression buildQualifiedTypeAccess(ITypeExpression root, JsonObjec } else { throw new ParseError("Unsupported syntax in import type", getSourceLocation(node).getStart()); } - MemberExpression member = new MemberExpression(getSourceLocation(node), (Expression) base, name, false); + MemberExpression member = new MemberExpression(getSourceLocation(node), (Expression) base, name, false, false, false); attachSymbolInformation(member, node); return member; } @@ -1827,7 +1827,7 @@ private String getOperator(JsonObject node) throws ParseError { private Node convertPropertyAccessExpression(JsonObject node, SourceLocation loc) throws ParseError { - return new MemberExpression(loc, convertChild(node, "expression"), convertChild(node, "name"), false); + return new MemberExpression(loc, convertChild(node, "expression"), convertChild(node, "name"), false, false, false); } private Node convertPropertyAssignment(JsonObject node, @@ -1868,7 +1868,7 @@ private Node convertRestType(JsonObject node, SourceLocation loc) throws ParseEr } private Node convertQualifiedName(JsonObject node, SourceLocation loc) throws ParseError { - MemberExpression expr = new MemberExpression(loc, convertChild(node, "left"), convertChild(node, "right"), false); + MemberExpression expr = new MemberExpression(loc, convertChild(node, "left"), convertChild(node, "right"), false, false, false); attachSymbolInformation(expr, node); return expr; } diff --git a/javascript/extractor/tests/esnext/input/optional-chaining.js b/javascript/extractor/tests/esnext/input/optional-chaining.js new file mode 100644 index 000000000000..9936a915690b --- /dev/null +++ b/javascript/extractor/tests/esnext/input/optional-chaining.js @@ -0,0 +1,13 @@ +a1?.b1; + +a2?.[x2]; + +a3?.b3(); + +a4?.(); + +o5?.3:2; + +a6?.b6[x6].c6?.(y6).d6; + +delete a?.b \ No newline at end of file diff --git a/javascript/extractor/tests/esnext/input/optional-chaining_bad1.js b/javascript/extractor/tests/esnext/input/optional-chaining_bad1.js new file mode 100644 index 000000000000..3a5a910b4fe9 --- /dev/null +++ b/javascript/extractor/tests/esnext/input/optional-chaining_bad1.js @@ -0,0 +1 @@ +new a?.(); \ No newline at end of file diff --git a/javascript/extractor/tests/esnext/input/optional-chaining_bad2.js b/javascript/extractor/tests/esnext/input/optional-chaining_bad2.js new file mode 100644 index 000000000000..c0fde8ceebf0 --- /dev/null +++ b/javascript/extractor/tests/esnext/input/optional-chaining_bad2.js @@ -0,0 +1 @@ +a?.`{b}`; \ No newline at end of file diff --git a/javascript/extractor/tests/esnext/input/optional-chaining_bad3.js b/javascript/extractor/tests/esnext/input/optional-chaining_bad3.js new file mode 100644 index 000000000000..7e36fa888438 --- /dev/null +++ b/javascript/extractor/tests/esnext/input/optional-chaining_bad3.js @@ -0,0 +1 @@ +new a?.b(); \ No newline at end of file diff --git a/javascript/extractor/tests/esnext/input/optional-chaining_bad4.js b/javascript/extractor/tests/esnext/input/optional-chaining_bad4.js new file mode 100644 index 000000000000..544617959884 --- /dev/null +++ b/javascript/extractor/tests/esnext/input/optional-chaining_bad4.js @@ -0,0 +1 @@ +a?.b`{c}`; \ No newline at end of file diff --git a/javascript/extractor/tests/esnext/input/optional-chaining_bad5.js b/javascript/extractor/tests/esnext/input/optional-chaining_bad5.js new file mode 100644 index 000000000000..66cac7597088 --- /dev/null +++ b/javascript/extractor/tests/esnext/input/optional-chaining_bad5.js @@ -0,0 +1 @@ +a?.b = c; \ No newline at end of file diff --git a/javascript/extractor/tests/esnext/input/optional-chaining_bad6.js b/javascript/extractor/tests/esnext/input/optional-chaining_bad6.js new file mode 100644 index 000000000000..7d5eb2e9e734 --- /dev/null +++ b/javascript/extractor/tests/esnext/input/optional-chaining_bad6.js @@ -0,0 +1 @@ +new a.b?.c(); diff --git a/javascript/extractor/tests/esnext/input/optional-chaining_short-circuiting.js b/javascript/extractor/tests/esnext/input/optional-chaining_short-circuiting.js new file mode 100644 index 000000000000..659cbea8924d --- /dev/null +++ b/javascript/extractor/tests/esnext/input/optional-chaining_short-circuiting.js @@ -0,0 +1,29 @@ +a?.b.c(++x).d; + +a?.b[3].c?.(x).d; + +(a?.b).c; + +(a?.b.c).d; + +a?.[b?.c?.d].e?.f; + +a?.()[b?.().c?.().d].e?.().f; + +if (a?.b) { + true; +} else { + false; +} + +if (!a?.b) { + true; +} else { + false; +} + +if (a?.b && c?.d) { + true; +} else { + false; +} diff --git a/javascript/extractor/tests/esnext/output/trap/optional-chaining.js.trap b/javascript/extractor/tests/esnext/output/trap/optional-chaining.js.trap new file mode 100644 index 000000000000..cc949812b603 --- /dev/null +++ b/javascript/extractor/tests/esnext/output/trap/optional-chaining.js.trap @@ -0,0 +1,655 @@ +#10000=@"/optional-chaining.js;sourcefile" +files(#10000,"/optional-chaining.js","optional-chaining","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,13,11" +locations_default(#20002,#10000,1,1,13,11) +hasLocation(#20001,#20002) +#20003=* +stmts(#20003,2,#20001,0,"a1?.b1;") +#20004=@"loc,{#10000},1,1,1,7" +locations_default(#20004,#10000,1,1,1,7) +hasLocation(#20003,#20004) +stmtContainers(#20003,#20001) +#20005=* +exprs(#20005,14,#20003,0,"a1?.b1") +#20006=@"loc,{#10000},1,1,1,6" +locations_default(#20006,#10000,1,1,1,6) +hasLocation(#20005,#20006) +enclosingStmt(#20005,#20003) +exprContainers(#20005,#20001) +#20007=* +exprs(#20007,79,#20005,0,"a1") +#20008=@"loc,{#10000},1,1,1,2" +locations_default(#20008,#10000,1,1,1,2) +hasLocation(#20007,#20008) +enclosingStmt(#20007,#20003) +exprContainers(#20007,#20001) +literals("a1","a1",#20007) +#20009=@"var;{a1};{#20000}" +variables(#20009,"a1",#20000) +bind(#20007,#20009) +#20010=* +exprs(#20010,0,#20005,1,"b1") +#20011=@"loc,{#10000},1,5,1,6" +locations_default(#20011,#10000,1,5,1,6) +hasLocation(#20010,#20011) +enclosingStmt(#20010,#20003) +exprContainers(#20010,#20001) +literals("b1","b1",#20010) +isOptionalChaining(#20005) +#20012=* +stmts(#20012,2,#20001,1,"a2?.[x2];") +#20013=@"loc,{#10000},3,1,3,9" +locations_default(#20013,#10000,3,1,3,9) +hasLocation(#20012,#20013) +stmtContainers(#20012,#20001) +#20014=* +exprs(#20014,15,#20012,0,"a2?.[x2]") +#20015=@"loc,{#10000},3,1,3,8" +locations_default(#20015,#10000,3,1,3,8) +hasLocation(#20014,#20015) +enclosingStmt(#20014,#20012) +exprContainers(#20014,#20001) +#20016=* +exprs(#20016,79,#20014,0,"a2") +#20017=@"loc,{#10000},3,1,3,2" +locations_default(#20017,#10000,3,1,3,2) +hasLocation(#20016,#20017) +enclosingStmt(#20016,#20012) +exprContainers(#20016,#20001) +literals("a2","a2",#20016) +#20018=@"var;{a2};{#20000}" +variables(#20018,"a2",#20000) +bind(#20016,#20018) +#20019=* +exprs(#20019,79,#20014,1,"x2") +#20020=@"loc,{#10000},3,6,3,7" +locations_default(#20020,#10000,3,6,3,7) +hasLocation(#20019,#20020) +enclosingStmt(#20019,#20012) +exprContainers(#20019,#20001) +literals("x2","x2",#20019) +#20021=@"var;{x2};{#20000}" +variables(#20021,"x2",#20000) +bind(#20019,#20021) +isOptionalChaining(#20014) +#20022=* +stmts(#20022,2,#20001,2,"a3?.b3();") +#20023=@"loc,{#10000},5,1,5,9" +locations_default(#20023,#10000,5,1,5,9) +hasLocation(#20022,#20023) +stmtContainers(#20022,#20001) +#20024=* +exprs(#20024,13,#20022,0,"a3?.b3()") +#20025=@"loc,{#10000},5,1,5,8" +locations_default(#20025,#10000,5,1,5,8) +hasLocation(#20024,#20025) +enclosingStmt(#20024,#20022) +exprContainers(#20024,#20001) +#20026=* +exprs(#20026,14,#20024,-1,"a3?.b3") +#20027=@"loc,{#10000},5,1,5,6" +locations_default(#20027,#10000,5,1,5,6) +hasLocation(#20026,#20027) +enclosingStmt(#20026,#20022) +exprContainers(#20026,#20001) +#20028=* +exprs(#20028,79,#20026,0,"a3") +#20029=@"loc,{#10000},5,1,5,2" +locations_default(#20029,#10000,5,1,5,2) +hasLocation(#20028,#20029) +enclosingStmt(#20028,#20022) +exprContainers(#20028,#20001) +literals("a3","a3",#20028) +#20030=@"var;{a3};{#20000}" +variables(#20030,"a3",#20000) +bind(#20028,#20030) +#20031=* +exprs(#20031,0,#20026,1,"b3") +#20032=@"loc,{#10000},5,5,5,6" +locations_default(#20032,#10000,5,5,5,6) +hasLocation(#20031,#20032) +enclosingStmt(#20031,#20022) +exprContainers(#20031,#20001) +literals("b3","b3",#20031) +isOptionalChaining(#20026) +#20033=* +stmts(#20033,2,#20001,3,"a4?.();") +#20034=@"loc,{#10000},7,1,7,7" +locations_default(#20034,#10000,7,1,7,7) +hasLocation(#20033,#20034) +stmtContainers(#20033,#20001) +#20035=* +exprs(#20035,13,#20033,0,"a4?.()") +#20036=@"loc,{#10000},7,1,7,6" +locations_default(#20036,#10000,7,1,7,6) +hasLocation(#20035,#20036) +enclosingStmt(#20035,#20033) +exprContainers(#20035,#20001) +#20037=* +exprs(#20037,79,#20035,-1,"a4") +#20038=@"loc,{#10000},7,1,7,2" +locations_default(#20038,#10000,7,1,7,2) +hasLocation(#20037,#20038) +enclosingStmt(#20037,#20033) +exprContainers(#20037,#20001) +literals("a4","a4",#20037) +#20039=@"var;{a4};{#20000}" +variables(#20039,"a4",#20000) +bind(#20037,#20039) +isOptionalChaining(#20035) +#20040=* +stmts(#20040,2,#20001,4,"o5?.3:2;") +#20041=@"loc,{#10000},9,1,9,8" +locations_default(#20041,#10000,9,1,9,8) +hasLocation(#20040,#20041) +stmtContainers(#20040,#20001) +#20042=* +exprs(#20042,11,#20040,0,"o5?.3:2") +#20043=@"loc,{#10000},9,1,9,7" +locations_default(#20043,#10000,9,1,9,7) +hasLocation(#20042,#20043) +enclosingStmt(#20042,#20040) +exprContainers(#20042,#20001) +#20044=* +exprs(#20044,79,#20042,0,"o5") +#20045=@"loc,{#10000},9,1,9,2" +locations_default(#20045,#10000,9,1,9,2) +hasLocation(#20044,#20045) +enclosingStmt(#20044,#20040) +exprContainers(#20044,#20001) +literals("o5","o5",#20044) +#20046=@"var;{o5};{#20000}" +variables(#20046,"o5",#20000) +bind(#20044,#20046) +#20047=* +exprs(#20047,3,#20042,1,".3") +#20048=@"loc,{#10000},9,4,9,5" +locations_default(#20048,#10000,9,4,9,5) +hasLocation(#20047,#20048) +enclosingStmt(#20047,#20040) +exprContainers(#20047,#20001) +literals("0.3",".3",#20047) +#20049=* +exprs(#20049,3,#20042,2,"2") +#20050=@"loc,{#10000},9,7,9,7" +locations_default(#20050,#10000,9,7,9,7) +hasLocation(#20049,#20050) +enclosingStmt(#20049,#20040) +exprContainers(#20049,#20001) +literals("2","2",#20049) +#20051=* +stmts(#20051,2,#20001,5,"a6?.b6[ ... y6).d6;") +#20052=@"loc,{#10000},11,1,11,23" +locations_default(#20052,#10000,11,1,11,23) +hasLocation(#20051,#20052) +stmtContainers(#20051,#20001) +#20053=* +exprs(#20053,14,#20051,0,"a6?.b6[ ... (y6).d6") +#20054=@"loc,{#10000},11,1,11,22" +locations_default(#20054,#10000,11,1,11,22) +hasLocation(#20053,#20054) +enclosingStmt(#20053,#20051) +exprContainers(#20053,#20001) +#20055=* +exprs(#20055,13,#20053,0,"a6?.b6[x6].c6?.(y6)") +#20056=@"loc,{#10000},11,1,11,19" +locations_default(#20056,#10000,11,1,11,19) +hasLocation(#20055,#20056) +enclosingStmt(#20055,#20051) +exprContainers(#20055,#20001) +#20057=* +exprs(#20057,14,#20055,-1,"a6?.b6[x6].c6") +#20058=@"loc,{#10000},11,1,11,13" +locations_default(#20058,#10000,11,1,11,13) +hasLocation(#20057,#20058) +enclosingStmt(#20057,#20051) +exprContainers(#20057,#20001) +#20059=* +exprs(#20059,15,#20057,0,"a6?.b6[x6]") +#20060=@"loc,{#10000},11,1,11,10" +locations_default(#20060,#10000,11,1,11,10) +hasLocation(#20059,#20060) +enclosingStmt(#20059,#20051) +exprContainers(#20059,#20001) +#20061=* +exprs(#20061,14,#20059,0,"a6?.b6") +#20062=@"loc,{#10000},11,1,11,6" +locations_default(#20062,#10000,11,1,11,6) +hasLocation(#20061,#20062) +enclosingStmt(#20061,#20051) +exprContainers(#20061,#20001) +#20063=* +exprs(#20063,79,#20061,0,"a6") +#20064=@"loc,{#10000},11,1,11,2" +locations_default(#20064,#10000,11,1,11,2) +hasLocation(#20063,#20064) +enclosingStmt(#20063,#20051) +exprContainers(#20063,#20001) +literals("a6","a6",#20063) +#20065=@"var;{a6};{#20000}" +variables(#20065,"a6",#20000) +bind(#20063,#20065) +#20066=* +exprs(#20066,0,#20061,1,"b6") +#20067=@"loc,{#10000},11,5,11,6" +locations_default(#20067,#10000,11,5,11,6) +hasLocation(#20066,#20067) +enclosingStmt(#20066,#20051) +exprContainers(#20066,#20001) +literals("b6","b6",#20066) +isOptionalChaining(#20061) +#20068=* +exprs(#20068,79,#20059,1,"x6") +#20069=@"loc,{#10000},11,8,11,9" +locations_default(#20069,#10000,11,8,11,9) +hasLocation(#20068,#20069) +enclosingStmt(#20068,#20051) +exprContainers(#20068,#20001) +literals("x6","x6",#20068) +#20070=@"var;{x6};{#20000}" +variables(#20070,"x6",#20000) +bind(#20068,#20070) +#20071=* +exprs(#20071,0,#20057,1,"c6") +#20072=@"loc,{#10000},11,12,11,13" +locations_default(#20072,#10000,11,12,11,13) +hasLocation(#20071,#20072) +enclosingStmt(#20071,#20051) +exprContainers(#20071,#20001) +literals("c6","c6",#20071) +#20073=* +exprs(#20073,79,#20055,0,"y6") +#20074=@"loc,{#10000},11,17,11,18" +locations_default(#20074,#10000,11,17,11,18) +hasLocation(#20073,#20074) +enclosingStmt(#20073,#20051) +exprContainers(#20073,#20001) +literals("y6","y6",#20073) +#20075=@"var;{y6};{#20000}" +variables(#20075,"y6",#20000) +bind(#20073,#20075) +isOptionalChaining(#20055) +#20076=* +exprs(#20076,0,#20053,1,"d6") +#20077=@"loc,{#10000},11,21,11,22" +locations_default(#20077,#10000,11,21,11,22) +hasLocation(#20076,#20077) +enclosingStmt(#20076,#20051) +exprContainers(#20076,#20001) +literals("d6","d6",#20076) +#20078=* +stmts(#20078,2,#20001,6,"delete a?.b") +#20079=@"loc,{#10000},13,1,13,11" +locations_default(#20079,#10000,13,1,13,11) +hasLocation(#20078,#20079) +stmtContainers(#20078,#20001) +#20080=* +exprs(#20080,22,#20078,0,"delete a?.b") +hasLocation(#20080,#20079) +enclosingStmt(#20080,#20078) +exprContainers(#20080,#20001) +#20081=* +exprs(#20081,14,#20080,0,"a?.b") +#20082=@"loc,{#10000},13,8,13,11" +locations_default(#20082,#10000,13,8,13,11) +hasLocation(#20081,#20082) +enclosingStmt(#20081,#20078) +exprContainers(#20081,#20001) +#20083=* +exprs(#20083,79,#20081,0,"a") +#20084=@"loc,{#10000},13,8,13,8" +locations_default(#20084,#10000,13,8,13,8) +hasLocation(#20083,#20084) +enclosingStmt(#20083,#20078) +exprContainers(#20083,#20001) +literals("a","a",#20083) +#20085=@"var;{a};{#20000}" +variables(#20085,"a",#20000) +bind(#20083,#20085) +#20086=* +exprs(#20086,0,#20081,1,"b") +#20087=@"loc,{#10000},13,11,13,11" +locations_default(#20087,#10000,13,11,13,11) +hasLocation(#20086,#20087) +enclosingStmt(#20086,#20078) +exprContainers(#20086,#20001) +literals("b","b",#20086) +isOptionalChaining(#20081) +#20088=* +lines(#20088,#20001,"a1?.b1;"," +") +hasLocation(#20088,#20004) +#20089=* +lines(#20089,#20001,""," +") +#20090=@"loc,{#10000},2,1,2,0" +locations_default(#20090,#10000,2,1,2,0) +hasLocation(#20089,#20090) +#20091=* +lines(#20091,#20001,"a2?.[x2];"," +") +hasLocation(#20091,#20013) +#20092=* +lines(#20092,#20001,""," +") +#20093=@"loc,{#10000},4,1,4,0" +locations_default(#20093,#10000,4,1,4,0) +hasLocation(#20092,#20093) +#20094=* +lines(#20094,#20001,"a3?.b3();"," +") +hasLocation(#20094,#20023) +#20095=* +lines(#20095,#20001,""," +") +#20096=@"loc,{#10000},6,1,6,0" +locations_default(#20096,#10000,6,1,6,0) +hasLocation(#20095,#20096) +#20097=* +lines(#20097,#20001,"a4?.();"," +") +hasLocation(#20097,#20034) +#20098=* +lines(#20098,#20001,""," +") +#20099=@"loc,{#10000},8,1,8,0" +locations_default(#20099,#10000,8,1,8,0) +hasLocation(#20098,#20099) +#20100=* +lines(#20100,#20001,"o5?.3:2;"," +") +hasLocation(#20100,#20041) +#20101=* +lines(#20101,#20001,""," +") +#20102=@"loc,{#10000},10,1,10,0" +locations_default(#20102,#10000,10,1,10,0) +hasLocation(#20101,#20102) +#20103=* +lines(#20103,#20001,"a6?.b6[x6].c6?.(y6).d6;"," +") +hasLocation(#20103,#20052) +#20104=* +lines(#20104,#20001,""," +") +#20105=@"loc,{#10000},12,1,12,0" +locations_default(#20105,#10000,12,1,12,0) +hasLocation(#20104,#20105) +#20106=* +lines(#20106,#20001,"delete a?.b","") +hasLocation(#20106,#20079) +numlines(#20001,13,7,0) +#20107=* +tokeninfo(#20107,6,#20001,0,"a1") +hasLocation(#20107,#20008) +#20108=* +tokeninfo(#20108,8,#20001,1,"?.") +#20109=@"loc,{#10000},1,3,1,4" +locations_default(#20109,#10000,1,3,1,4) +hasLocation(#20108,#20109) +#20110=* +tokeninfo(#20110,6,#20001,2,"b1") +hasLocation(#20110,#20011) +#20111=* +tokeninfo(#20111,8,#20001,3,";") +#20112=@"loc,{#10000},1,7,1,7" +locations_default(#20112,#10000,1,7,1,7) +hasLocation(#20111,#20112) +#20113=* +tokeninfo(#20113,6,#20001,4,"a2") +hasLocation(#20113,#20017) +#20114=* +tokeninfo(#20114,8,#20001,5,"?.") +#20115=@"loc,{#10000},3,3,3,4" +locations_default(#20115,#10000,3,3,3,4) +hasLocation(#20114,#20115) +#20116=* +tokeninfo(#20116,8,#20001,6,"[") +#20117=@"loc,{#10000},3,5,3,5" +locations_default(#20117,#10000,3,5,3,5) +hasLocation(#20116,#20117) +#20118=* +tokeninfo(#20118,6,#20001,7,"x2") +hasLocation(#20118,#20020) +#20119=* +tokeninfo(#20119,8,#20001,8,"]") +#20120=@"loc,{#10000},3,8,3,8" +locations_default(#20120,#10000,3,8,3,8) +hasLocation(#20119,#20120) +#20121=* +tokeninfo(#20121,8,#20001,9,";") +#20122=@"loc,{#10000},3,9,3,9" +locations_default(#20122,#10000,3,9,3,9) +hasLocation(#20121,#20122) +#20123=* +tokeninfo(#20123,6,#20001,10,"a3") +hasLocation(#20123,#20029) +#20124=* +tokeninfo(#20124,8,#20001,11,"?.") +#20125=@"loc,{#10000},5,3,5,4" +locations_default(#20125,#10000,5,3,5,4) +hasLocation(#20124,#20125) +#20126=* +tokeninfo(#20126,6,#20001,12,"b3") +hasLocation(#20126,#20032) +#20127=* +tokeninfo(#20127,8,#20001,13,"(") +#20128=@"loc,{#10000},5,7,5,7" +locations_default(#20128,#10000,5,7,5,7) +hasLocation(#20127,#20128) +#20129=* +tokeninfo(#20129,8,#20001,14,")") +#20130=@"loc,{#10000},5,8,5,8" +locations_default(#20130,#10000,5,8,5,8) +hasLocation(#20129,#20130) +#20131=* +tokeninfo(#20131,8,#20001,15,";") +#20132=@"loc,{#10000},5,9,5,9" +locations_default(#20132,#10000,5,9,5,9) +hasLocation(#20131,#20132) +#20133=* +tokeninfo(#20133,6,#20001,16,"a4") +hasLocation(#20133,#20038) +#20134=* +tokeninfo(#20134,8,#20001,17,"?.") +#20135=@"loc,{#10000},7,3,7,4" +locations_default(#20135,#10000,7,3,7,4) +hasLocation(#20134,#20135) +#20136=* +tokeninfo(#20136,8,#20001,18,"(") +#20137=@"loc,{#10000},7,5,7,5" +locations_default(#20137,#10000,7,5,7,5) +hasLocation(#20136,#20137) +#20138=* +tokeninfo(#20138,8,#20001,19,")") +#20139=@"loc,{#10000},7,6,7,6" +locations_default(#20139,#10000,7,6,7,6) +hasLocation(#20138,#20139) +#20140=* +tokeninfo(#20140,8,#20001,20,";") +#20141=@"loc,{#10000},7,7,7,7" +locations_default(#20141,#10000,7,7,7,7) +hasLocation(#20140,#20141) +#20142=* +tokeninfo(#20142,6,#20001,21,"o5") +hasLocation(#20142,#20045) +#20143=* +tokeninfo(#20143,8,#20001,22,"?") +#20144=@"loc,{#10000},9,3,9,3" +locations_default(#20144,#10000,9,3,9,3) +hasLocation(#20143,#20144) +#20145=* +tokeninfo(#20145,3,#20001,23,".3") +hasLocation(#20145,#20048) +#20146=* +tokeninfo(#20146,8,#20001,24,":") +#20147=@"loc,{#10000},9,6,9,6" +locations_default(#20147,#10000,9,6,9,6) +hasLocation(#20146,#20147) +#20148=* +tokeninfo(#20148,3,#20001,25,"2") +hasLocation(#20148,#20050) +#20149=* +tokeninfo(#20149,8,#20001,26,";") +#20150=@"loc,{#10000},9,8,9,8" +locations_default(#20150,#10000,9,8,9,8) +hasLocation(#20149,#20150) +#20151=* +tokeninfo(#20151,6,#20001,27,"a6") +hasLocation(#20151,#20064) +#20152=* +tokeninfo(#20152,8,#20001,28,"?.") +#20153=@"loc,{#10000},11,3,11,4" +locations_default(#20153,#10000,11,3,11,4) +hasLocation(#20152,#20153) +#20154=* +tokeninfo(#20154,6,#20001,29,"b6") +hasLocation(#20154,#20067) +#20155=* +tokeninfo(#20155,8,#20001,30,"[") +#20156=@"loc,{#10000},11,7,11,7" +locations_default(#20156,#10000,11,7,11,7) +hasLocation(#20155,#20156) +#20157=* +tokeninfo(#20157,6,#20001,31,"x6") +hasLocation(#20157,#20069) +#20158=* +tokeninfo(#20158,8,#20001,32,"]") +#20159=@"loc,{#10000},11,10,11,10" +locations_default(#20159,#10000,11,10,11,10) +hasLocation(#20158,#20159) +#20160=* +tokeninfo(#20160,8,#20001,33,".") +#20161=@"loc,{#10000},11,11,11,11" +locations_default(#20161,#10000,11,11,11,11) +hasLocation(#20160,#20161) +#20162=* +tokeninfo(#20162,6,#20001,34,"c6") +hasLocation(#20162,#20072) +#20163=* +tokeninfo(#20163,8,#20001,35,"?.") +#20164=@"loc,{#10000},11,14,11,15" +locations_default(#20164,#10000,11,14,11,15) +hasLocation(#20163,#20164) +#20165=* +tokeninfo(#20165,8,#20001,36,"(") +#20166=@"loc,{#10000},11,16,11,16" +locations_default(#20166,#10000,11,16,11,16) +hasLocation(#20165,#20166) +#20167=* +tokeninfo(#20167,6,#20001,37,"y6") +hasLocation(#20167,#20074) +#20168=* +tokeninfo(#20168,8,#20001,38,")") +#20169=@"loc,{#10000},11,19,11,19" +locations_default(#20169,#10000,11,19,11,19) +hasLocation(#20168,#20169) +#20170=* +tokeninfo(#20170,8,#20001,39,".") +#20171=@"loc,{#10000},11,20,11,20" +locations_default(#20171,#10000,11,20,11,20) +hasLocation(#20170,#20171) +#20172=* +tokeninfo(#20172,6,#20001,40,"d6") +hasLocation(#20172,#20077) +#20173=* +tokeninfo(#20173,8,#20001,41,";") +#20174=@"loc,{#10000},11,23,11,23" +locations_default(#20174,#10000,11,23,11,23) +hasLocation(#20173,#20174) +#20175=* +tokeninfo(#20175,7,#20001,42,"delete") +#20176=@"loc,{#10000},13,1,13,6" +locations_default(#20176,#10000,13,1,13,6) +hasLocation(#20175,#20176) +#20177=* +tokeninfo(#20177,6,#20001,43,"a") +hasLocation(#20177,#20084) +#20178=* +tokeninfo(#20178,8,#20001,44,"?.") +#20179=@"loc,{#10000},13,9,13,10" +locations_default(#20179,#10000,13,9,13,10) +hasLocation(#20178,#20179) +#20180=* +tokeninfo(#20180,6,#20001,45,"b") +hasLocation(#20180,#20087) +#20181=* +tokeninfo(#20181,0,#20001,46,"") +#20182=@"loc,{#10000},13,12,13,11" +locations_default(#20182,#10000,13,12,13,11) +hasLocation(#20181,#20182) +#20183=* +entry_cfg_node(#20183,#20001) +#20184=@"loc,{#10000},1,1,1,0" +locations_default(#20184,#10000,1,1,1,0) +hasLocation(#20183,#20184) +#20185=* +exit_cfg_node(#20185,#20001) +hasLocation(#20185,#20182) +successor(#20078,#20083) +successor(#20086,#20081) +successor(#20083,#20086) +successor(#20081,#20080) +successor(#20083,#20080) +successor(#20080,#20185) +successor(#20051,#20063) +successor(#20076,#20053) +successor(#20073,#20055) +successor(#20071,#20057) +successor(#20068,#20059) +successor(#20066,#20061) +successor(#20063,#20066) +successor(#20061,#20068) +successor(#20063,#20078) +successor(#20059,#20071) +successor(#20057,#20073) +successor(#20055,#20076) +successor(#20057,#20078) +successor(#20053,#20078) +successor(#20040,#20042) +successor(#20042,#20044) +#20186=* +guard_node(#20186,1,#20044) +hasLocation(#20186,#20045) +successor(#20186,#20047) +#20187=* +guard_node(#20187,0,#20044) +hasLocation(#20187,#20045) +successor(#20187,#20049) +successor(#20044,#20186) +successor(#20044,#20187) +successor(#20047,#20051) +successor(#20049,#20051) +successor(#20033,#20037) +successor(#20037,#20035) +successor(#20035,#20040) +successor(#20037,#20040) +successor(#20022,#20028) +successor(#20031,#20026) +successor(#20028,#20031) +successor(#20026,#20024) +successor(#20028,#20033) +successor(#20024,#20033) +successor(#20012,#20016) +successor(#20019,#20014) +successor(#20016,#20019) +successor(#20014,#20022) +successor(#20016,#20022) +successor(#20003,#20007) +successor(#20010,#20005) +successor(#20007,#20010) +successor(#20005,#20012) +successor(#20007,#20012) +successor(#20183,#20003) +numlines(#10000,13,7,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad1.js.trap b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad1.js.trap new file mode 100644 index 000000000000..52d3ee0229d7 --- /dev/null +++ b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad1.js.trap @@ -0,0 +1,28 @@ +#10000=@"/optional-chaining_bad1.js;sourcefile" +files(#10000,"/optional-chaining_bad1.js","optional-chaining_bad1","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,1,1" +locations_default(#20002,#10000,1,1,1,1) +hasLocation(#20001,#20002) +#20003=* +jsParseErrors(#20003,#20001,"Error: Unexpected token","new a?.();") +#20004=@"loc,{#10000},1,8,1,8" +locations_default(#20004,#10000,1,8,1,8) +hasLocation(#20003,#20004) +#20005=* +lines(#20005,#20001,"new a?.();","") +#20006=@"loc,{#10000},1,1,1,10" +locations_default(#20006,#10000,1,1,1,10) +hasLocation(#20005,#20006) +numlines(#20001,1,0,0) +numlines(#10000,1,0,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad2.js.trap b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad2.js.trap new file mode 100644 index 000000000000..81d26f56f265 --- /dev/null +++ b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad2.js.trap @@ -0,0 +1,26 @@ +#10000=@"/optional-chaining_bad2.js;sourcefile" +files(#10000,"/optional-chaining_bad2.js","optional-chaining_bad2","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,1,1" +locations_default(#20002,#10000,1,1,1,1) +hasLocation(#20001,#20002) +#20003=* +jsParseErrors(#20003,#20001,"Error: An optional chain may not be used in a tagged template expression.","a?.`{b}`;") +hasLocation(#20003,#20002) +#20004=* +lines(#20004,#20001,"a?.`{b}`;","") +#20005=@"loc,{#10000},1,1,1,9" +locations_default(#20005,#10000,1,1,1,9) +hasLocation(#20004,#20005) +numlines(#20001,1,0,0) +numlines(#10000,1,0,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad3.js.trap b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad3.js.trap new file mode 100644 index 000000000000..e7c42643fceb --- /dev/null +++ b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad3.js.trap @@ -0,0 +1,28 @@ +#10000=@"/optional-chaining_bad3.js;sourcefile" +files(#10000,"/optional-chaining_bad3.js","optional-chaining_bad3","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,1,1" +locations_default(#20002,#10000,1,1,1,1) +hasLocation(#20001,#20002) +#20003=* +jsParseErrors(#20003,#20001,"Error: An optional chain may not be used in a `new` expression.","new a?.b();") +#20004=@"loc,{#10000},1,5,1,5" +locations_default(#20004,#10000,1,5,1,5) +hasLocation(#20003,#20004) +#20005=* +lines(#20005,#20001,"new a?.b();","") +#20006=@"loc,{#10000},1,1,1,11" +locations_default(#20006,#10000,1,1,1,11) +hasLocation(#20005,#20006) +numlines(#20001,1,0,0) +numlines(#10000,1,0,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad4.js.trap b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad4.js.trap new file mode 100644 index 000000000000..19386282c210 --- /dev/null +++ b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad4.js.trap @@ -0,0 +1,26 @@ +#10000=@"/optional-chaining_bad4.js;sourcefile" +files(#10000,"/optional-chaining_bad4.js","optional-chaining_bad4","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,1,1" +locations_default(#20002,#10000,1,1,1,1) +hasLocation(#20001,#20002) +#20003=* +jsParseErrors(#20003,#20001,"Error: An optional chain may not be used in a tagged template expression.","a?.b`{c}`;") +hasLocation(#20003,#20002) +#20004=* +lines(#20004,#20001,"a?.b`{c}`;","") +#20005=@"loc,{#10000},1,1,1,10" +locations_default(#20005,#10000,1,1,1,10) +hasLocation(#20004,#20005) +numlines(#20001,1,0,0) +numlines(#10000,1,0,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad5.js.trap b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad5.js.trap new file mode 100644 index 000000000000..ceb1648c62ba --- /dev/null +++ b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad5.js.trap @@ -0,0 +1,26 @@ +#10000=@"/optional-chaining_bad5.js;sourcefile" +files(#10000,"/optional-chaining_bad5.js","optional-chaining_bad5","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,1,1" +locations_default(#20002,#10000,1,1,1,1) +hasLocation(#20001,#20002) +#20003=* +jsParseErrors(#20003,#20001,"Error: Invalid left-hand side in assignment","a?.b = c;") +hasLocation(#20003,#20002) +#20004=* +lines(#20004,#20001,"a?.b = c;","") +#20005=@"loc,{#10000},1,1,1,9" +locations_default(#20005,#10000,1,1,1,9) +hasLocation(#20004,#20005) +numlines(#20001,1,0,0) +numlines(#10000,1,0,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad6.js.trap b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad6.js.trap new file mode 100644 index 000000000000..f328b18d0c11 --- /dev/null +++ b/javascript/extractor/tests/esnext/output/trap/optional-chaining_bad6.js.trap @@ -0,0 +1,30 @@ +#10000=@"/optional-chaining_bad6.js;sourcefile" +files(#10000,"/optional-chaining_bad6.js","optional-chaining_bad6","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,1,1" +locations_default(#20002,#10000,1,1,1,1) +hasLocation(#20001,#20002) +#20003=* +jsParseErrors(#20003,#20001,"Error: An optional chain may not be used in a `new` expression.","new a.b?.c(); +") +#20004=@"loc,{#10000},1,5,1,5" +locations_default(#20004,#10000,1,5,1,5) +hasLocation(#20003,#20004) +#20005=* +lines(#20005,#20001,"new a.b?.c();"," +") +#20006=@"loc,{#10000},1,1,1,13" +locations_default(#20006,#10000,1,1,1,13) +hasLocation(#20005,#20006) +numlines(#20001,1,0,0) +numlines(#10000,1,0,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/esnext/output/trap/optional-chaining_short-circuiting.js.trap b/javascript/extractor/tests/esnext/output/trap/optional-chaining_short-circuiting.js.trap new file mode 100644 index 000000000000..e39ceb7acd5a --- /dev/null +++ b/javascript/extractor/tests/esnext/output/trap/optional-chaining_short-circuiting.js.trap @@ -0,0 +1,1707 @@ +#10000=@"/optional-chaining_short-circuiting.js;sourcefile" +files(#10000,"/optional-chaining_short-circuiting.js","optional-chaining_short-circuiting","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,30,0" +locations_default(#20002,#10000,1,1,30,0) +hasLocation(#20001,#20002) +#20003=* +stmts(#20003,2,#20001,0,"a?.b.c(++x).d;") +#20004=@"loc,{#10000},1,1,1,14" +locations_default(#20004,#10000,1,1,1,14) +hasLocation(#20003,#20004) +stmtContainers(#20003,#20001) +#20005=* +exprs(#20005,14,#20003,0,"a?.b.c(++x).d") +#20006=@"loc,{#10000},1,1,1,13" +locations_default(#20006,#10000,1,1,1,13) +hasLocation(#20005,#20006) +enclosingStmt(#20005,#20003) +exprContainers(#20005,#20001) +#20007=* +exprs(#20007,13,#20005,0,"a?.b.c(++x)") +#20008=@"loc,{#10000},1,1,1,11" +locations_default(#20008,#10000,1,1,1,11) +hasLocation(#20007,#20008) +enclosingStmt(#20007,#20003) +exprContainers(#20007,#20001) +#20009=* +exprs(#20009,14,#20007,-1,"a?.b.c") +#20010=@"loc,{#10000},1,1,1,6" +locations_default(#20010,#10000,1,1,1,6) +hasLocation(#20009,#20010) +enclosingStmt(#20009,#20003) +exprContainers(#20009,#20001) +#20011=* +exprs(#20011,14,#20009,0,"a?.b") +#20012=@"loc,{#10000},1,1,1,4" +locations_default(#20012,#10000,1,1,1,4) +hasLocation(#20011,#20012) +enclosingStmt(#20011,#20003) +exprContainers(#20011,#20001) +#20013=* +exprs(#20013,79,#20011,0,"a") +#20014=@"loc,{#10000},1,1,1,1" +locations_default(#20014,#10000,1,1,1,1) +hasLocation(#20013,#20014) +enclosingStmt(#20013,#20003) +exprContainers(#20013,#20001) +literals("a","a",#20013) +#20015=@"var;{a};{#20000}" +variables(#20015,"a",#20000) +bind(#20013,#20015) +#20016=* +exprs(#20016,0,#20011,1,"b") +#20017=@"loc,{#10000},1,4,1,4" +locations_default(#20017,#10000,1,4,1,4) +hasLocation(#20016,#20017) +enclosingStmt(#20016,#20003) +exprContainers(#20016,#20001) +literals("b","b",#20016) +isOptionalChaining(#20011) +#20018=* +exprs(#20018,0,#20009,1,"c") +#20019=@"loc,{#10000},1,6,1,6" +locations_default(#20019,#10000,1,6,1,6) +hasLocation(#20018,#20019) +enclosingStmt(#20018,#20003) +exprContainers(#20018,#20001) +literals("c","c",#20018) +#20020=* +exprs(#20020,59,#20007,0,"++x") +#20021=@"loc,{#10000},1,8,1,10" +locations_default(#20021,#10000,1,8,1,10) +hasLocation(#20020,#20021) +enclosingStmt(#20020,#20003) +exprContainers(#20020,#20001) +#20022=* +exprs(#20022,79,#20020,0,"x") +#20023=@"loc,{#10000},1,10,1,10" +locations_default(#20023,#10000,1,10,1,10) +hasLocation(#20022,#20023) +enclosingStmt(#20022,#20003) +exprContainers(#20022,#20001) +literals("x","x",#20022) +#20024=@"var;{x};{#20000}" +variables(#20024,"x",#20000) +bind(#20022,#20024) +#20025=* +exprs(#20025,0,#20005,1,"d") +#20026=@"loc,{#10000},1,13,1,13" +locations_default(#20026,#10000,1,13,1,13) +hasLocation(#20025,#20026) +enclosingStmt(#20025,#20003) +exprContainers(#20025,#20001) +literals("d","d",#20025) +#20027=* +stmts(#20027,2,#20001,1,"a?.b[3].c?.(x).d;") +#20028=@"loc,{#10000},3,1,3,17" +locations_default(#20028,#10000,3,1,3,17) +hasLocation(#20027,#20028) +stmtContainers(#20027,#20001) +#20029=* +exprs(#20029,14,#20027,0,"a?.b[3].c?.(x).d") +#20030=@"loc,{#10000},3,1,3,16" +locations_default(#20030,#10000,3,1,3,16) +hasLocation(#20029,#20030) +enclosingStmt(#20029,#20027) +exprContainers(#20029,#20001) +#20031=* +exprs(#20031,13,#20029,0,"a?.b[3].c?.(x)") +#20032=@"loc,{#10000},3,1,3,14" +locations_default(#20032,#10000,3,1,3,14) +hasLocation(#20031,#20032) +enclosingStmt(#20031,#20027) +exprContainers(#20031,#20001) +#20033=* +exprs(#20033,14,#20031,-1,"a?.b[3].c") +#20034=@"loc,{#10000},3,1,3,9" +locations_default(#20034,#10000,3,1,3,9) +hasLocation(#20033,#20034) +enclosingStmt(#20033,#20027) +exprContainers(#20033,#20001) +#20035=* +exprs(#20035,15,#20033,0,"a?.b[3]") +#20036=@"loc,{#10000},3,1,3,7" +locations_default(#20036,#10000,3,1,3,7) +hasLocation(#20035,#20036) +enclosingStmt(#20035,#20027) +exprContainers(#20035,#20001) +#20037=* +exprs(#20037,14,#20035,0,"a?.b") +#20038=@"loc,{#10000},3,1,3,4" +locations_default(#20038,#10000,3,1,3,4) +hasLocation(#20037,#20038) +enclosingStmt(#20037,#20027) +exprContainers(#20037,#20001) +#20039=* +exprs(#20039,79,#20037,0,"a") +#20040=@"loc,{#10000},3,1,3,1" +locations_default(#20040,#10000,3,1,3,1) +hasLocation(#20039,#20040) +enclosingStmt(#20039,#20027) +exprContainers(#20039,#20001) +literals("a","a",#20039) +bind(#20039,#20015) +#20041=* +exprs(#20041,0,#20037,1,"b") +#20042=@"loc,{#10000},3,4,3,4" +locations_default(#20042,#10000,3,4,3,4) +hasLocation(#20041,#20042) +enclosingStmt(#20041,#20027) +exprContainers(#20041,#20001) +literals("b","b",#20041) +isOptionalChaining(#20037) +#20043=* +exprs(#20043,3,#20035,1,"3") +#20044=@"loc,{#10000},3,6,3,6" +locations_default(#20044,#10000,3,6,3,6) +hasLocation(#20043,#20044) +enclosingStmt(#20043,#20027) +exprContainers(#20043,#20001) +literals("3","3",#20043) +#20045=* +exprs(#20045,0,#20033,1,"c") +#20046=@"loc,{#10000},3,9,3,9" +locations_default(#20046,#10000,3,9,3,9) +hasLocation(#20045,#20046) +enclosingStmt(#20045,#20027) +exprContainers(#20045,#20001) +literals("c","c",#20045) +#20047=* +exprs(#20047,79,#20031,0,"x") +#20048=@"loc,{#10000},3,13,3,13" +locations_default(#20048,#10000,3,13,3,13) +hasLocation(#20047,#20048) +enclosingStmt(#20047,#20027) +exprContainers(#20047,#20001) +literals("x","x",#20047) +bind(#20047,#20024) +isOptionalChaining(#20031) +#20049=* +exprs(#20049,0,#20029,1,"d") +#20050=@"loc,{#10000},3,16,3,16" +locations_default(#20050,#10000,3,16,3,16) +hasLocation(#20049,#20050) +enclosingStmt(#20049,#20027) +exprContainers(#20049,#20001) +literals("d","d",#20049) +#20051=* +stmts(#20051,2,#20001,2,"(a?.b).c;") +#20052=@"loc,{#10000},5,1,5,9" +locations_default(#20052,#10000,5,1,5,9) +hasLocation(#20051,#20052) +stmtContainers(#20051,#20001) +#20053=* +exprs(#20053,14,#20051,0,"(a?.b).c") +#20054=@"loc,{#10000},5,1,5,8" +locations_default(#20054,#10000,5,1,5,8) +hasLocation(#20053,#20054) +enclosingStmt(#20053,#20051) +exprContainers(#20053,#20001) +#20055=* +exprs(#20055,63,#20053,0,"(a?.b)") +#20056=@"loc,{#10000},5,1,5,6" +locations_default(#20056,#10000,5,1,5,6) +hasLocation(#20055,#20056) +enclosingStmt(#20055,#20051) +exprContainers(#20055,#20001) +#20057=* +exprs(#20057,14,#20055,0,"a?.b") +#20058=@"loc,{#10000},5,2,5,5" +locations_default(#20058,#10000,5,2,5,5) +hasLocation(#20057,#20058) +enclosingStmt(#20057,#20051) +exprContainers(#20057,#20001) +#20059=* +exprs(#20059,79,#20057,0,"a") +#20060=@"loc,{#10000},5,2,5,2" +locations_default(#20060,#10000,5,2,5,2) +hasLocation(#20059,#20060) +enclosingStmt(#20059,#20051) +exprContainers(#20059,#20001) +literals("a","a",#20059) +bind(#20059,#20015) +#20061=* +exprs(#20061,0,#20057,1,"b") +#20062=@"loc,{#10000},5,5,5,5" +locations_default(#20062,#10000,5,5,5,5) +hasLocation(#20061,#20062) +enclosingStmt(#20061,#20051) +exprContainers(#20061,#20001) +literals("b","b",#20061) +isOptionalChaining(#20057) +#20063=* +exprs(#20063,0,#20053,1,"c") +#20064=@"loc,{#10000},5,8,5,8" +locations_default(#20064,#10000,5,8,5,8) +hasLocation(#20063,#20064) +enclosingStmt(#20063,#20051) +exprContainers(#20063,#20001) +literals("c","c",#20063) +#20065=* +stmts(#20065,2,#20001,3,"(a?.b.c).d;") +#20066=@"loc,{#10000},7,1,7,11" +locations_default(#20066,#10000,7,1,7,11) +hasLocation(#20065,#20066) +stmtContainers(#20065,#20001) +#20067=* +exprs(#20067,14,#20065,0,"(a?.b.c).d") +#20068=@"loc,{#10000},7,1,7,10" +locations_default(#20068,#10000,7,1,7,10) +hasLocation(#20067,#20068) +enclosingStmt(#20067,#20065) +exprContainers(#20067,#20001) +#20069=* +exprs(#20069,63,#20067,0,"(a?.b.c)") +#20070=@"loc,{#10000},7,1,7,8" +locations_default(#20070,#10000,7,1,7,8) +hasLocation(#20069,#20070) +enclosingStmt(#20069,#20065) +exprContainers(#20069,#20001) +#20071=* +exprs(#20071,14,#20069,0,"a?.b.c") +#20072=@"loc,{#10000},7,2,7,7" +locations_default(#20072,#10000,7,2,7,7) +hasLocation(#20071,#20072) +enclosingStmt(#20071,#20065) +exprContainers(#20071,#20001) +#20073=* +exprs(#20073,14,#20071,0,"a?.b") +#20074=@"loc,{#10000},7,2,7,5" +locations_default(#20074,#10000,7,2,7,5) +hasLocation(#20073,#20074) +enclosingStmt(#20073,#20065) +exprContainers(#20073,#20001) +#20075=* +exprs(#20075,79,#20073,0,"a") +#20076=@"loc,{#10000},7,2,7,2" +locations_default(#20076,#10000,7,2,7,2) +hasLocation(#20075,#20076) +enclosingStmt(#20075,#20065) +exprContainers(#20075,#20001) +literals("a","a",#20075) +bind(#20075,#20015) +#20077=* +exprs(#20077,0,#20073,1,"b") +#20078=@"loc,{#10000},7,5,7,5" +locations_default(#20078,#10000,7,5,7,5) +hasLocation(#20077,#20078) +enclosingStmt(#20077,#20065) +exprContainers(#20077,#20001) +literals("b","b",#20077) +isOptionalChaining(#20073) +#20079=* +exprs(#20079,0,#20071,1,"c") +#20080=@"loc,{#10000},7,7,7,7" +locations_default(#20080,#10000,7,7,7,7) +hasLocation(#20079,#20080) +enclosingStmt(#20079,#20065) +exprContainers(#20079,#20001) +literals("c","c",#20079) +#20081=* +exprs(#20081,0,#20067,1,"d") +#20082=@"loc,{#10000},7,10,7,10" +locations_default(#20082,#10000,7,10,7,10) +hasLocation(#20081,#20082) +enclosingStmt(#20081,#20065) +exprContainers(#20081,#20001) +literals("d","d",#20081) +#20083=* +stmts(#20083,2,#20001,4,"a?.[b?.c?.d].e?.f;") +#20084=@"loc,{#10000},9,1,9,18" +locations_default(#20084,#10000,9,1,9,18) +hasLocation(#20083,#20084) +stmtContainers(#20083,#20001) +#20085=* +exprs(#20085,14,#20083,0,"a?.[b?.c?.d].e?.f") +#20086=@"loc,{#10000},9,1,9,17" +locations_default(#20086,#10000,9,1,9,17) +hasLocation(#20085,#20086) +enclosingStmt(#20085,#20083) +exprContainers(#20085,#20001) +#20087=* +exprs(#20087,14,#20085,0,"a?.[b?.c?.d].e") +#20088=@"loc,{#10000},9,1,9,14" +locations_default(#20088,#10000,9,1,9,14) +hasLocation(#20087,#20088) +enclosingStmt(#20087,#20083) +exprContainers(#20087,#20001) +#20089=* +exprs(#20089,15,#20087,0,"a?.[b?.c?.d]") +#20090=@"loc,{#10000},9,1,9,12" +locations_default(#20090,#10000,9,1,9,12) +hasLocation(#20089,#20090) +enclosingStmt(#20089,#20083) +exprContainers(#20089,#20001) +#20091=* +exprs(#20091,79,#20089,0,"a") +#20092=@"loc,{#10000},9,1,9,1" +locations_default(#20092,#10000,9,1,9,1) +hasLocation(#20091,#20092) +enclosingStmt(#20091,#20083) +exprContainers(#20091,#20001) +literals("a","a",#20091) +bind(#20091,#20015) +#20093=* +exprs(#20093,14,#20089,1,"b?.c?.d") +#20094=@"loc,{#10000},9,5,9,11" +locations_default(#20094,#10000,9,5,9,11) +hasLocation(#20093,#20094) +enclosingStmt(#20093,#20083) +exprContainers(#20093,#20001) +#20095=* +exprs(#20095,14,#20093,0,"b?.c") +#20096=@"loc,{#10000},9,5,9,8" +locations_default(#20096,#10000,9,5,9,8) +hasLocation(#20095,#20096) +enclosingStmt(#20095,#20083) +exprContainers(#20095,#20001) +#20097=* +exprs(#20097,79,#20095,0,"b") +#20098=@"loc,{#10000},9,5,9,5" +locations_default(#20098,#10000,9,5,9,5) +hasLocation(#20097,#20098) +enclosingStmt(#20097,#20083) +exprContainers(#20097,#20001) +literals("b","b",#20097) +#20099=@"var;{b};{#20000}" +variables(#20099,"b",#20000) +bind(#20097,#20099) +#20100=* +exprs(#20100,0,#20095,1,"c") +#20101=@"loc,{#10000},9,8,9,8" +locations_default(#20101,#10000,9,8,9,8) +hasLocation(#20100,#20101) +enclosingStmt(#20100,#20083) +exprContainers(#20100,#20001) +literals("c","c",#20100) +isOptionalChaining(#20095) +#20102=* +exprs(#20102,0,#20093,1,"d") +#20103=@"loc,{#10000},9,11,9,11" +locations_default(#20103,#10000,9,11,9,11) +hasLocation(#20102,#20103) +enclosingStmt(#20102,#20083) +exprContainers(#20102,#20001) +literals("d","d",#20102) +isOptionalChaining(#20093) +isOptionalChaining(#20089) +#20104=* +exprs(#20104,0,#20087,1,"e") +#20105=@"loc,{#10000},9,14,9,14" +locations_default(#20105,#10000,9,14,9,14) +hasLocation(#20104,#20105) +enclosingStmt(#20104,#20083) +exprContainers(#20104,#20001) +literals("e","e",#20104) +#20106=* +exprs(#20106,0,#20085,1,"f") +#20107=@"loc,{#10000},9,17,9,17" +locations_default(#20107,#10000,9,17,9,17) +hasLocation(#20106,#20107) +enclosingStmt(#20106,#20083) +exprContainers(#20106,#20001) +literals("f","f",#20106) +isOptionalChaining(#20085) +#20108=* +stmts(#20108,2,#20001,5,"a?.()[b ... ?.().f;") +#20109=@"loc,{#10000},11,1,11,29" +locations_default(#20109,#10000,11,1,11,29) +hasLocation(#20108,#20109) +stmtContainers(#20108,#20001) +#20110=* +exprs(#20110,14,#20108,0,"a?.()[b ... e?.().f") +#20111=@"loc,{#10000},11,1,11,28" +locations_default(#20111,#10000,11,1,11,28) +hasLocation(#20110,#20111) +enclosingStmt(#20110,#20108) +exprContainers(#20110,#20001) +#20112=* +exprs(#20112,13,#20110,0,"a?.()[b ... ].e?.()") +#20113=@"loc,{#10000},11,1,11,26" +locations_default(#20113,#10000,11,1,11,26) +hasLocation(#20112,#20113) +enclosingStmt(#20112,#20108) +exprContainers(#20112,#20001) +#20114=* +exprs(#20114,14,#20112,-1,"a?.()[b ... ().d].e") +#20115=@"loc,{#10000},11,1,11,22" +locations_default(#20115,#10000,11,1,11,22) +hasLocation(#20114,#20115) +enclosingStmt(#20114,#20108) +exprContainers(#20114,#20001) +#20116=* +exprs(#20116,15,#20114,0,"a?.()[b?.().c?.().d]") +#20117=@"loc,{#10000},11,1,11,20" +locations_default(#20117,#10000,11,1,11,20) +hasLocation(#20116,#20117) +enclosingStmt(#20116,#20108) +exprContainers(#20116,#20001) +#20118=* +exprs(#20118,13,#20116,0,"a?.()") +#20119=@"loc,{#10000},11,1,11,5" +locations_default(#20119,#10000,11,1,11,5) +hasLocation(#20118,#20119) +enclosingStmt(#20118,#20108) +exprContainers(#20118,#20001) +#20120=* +exprs(#20120,79,#20118,-1,"a") +#20121=@"loc,{#10000},11,1,11,1" +locations_default(#20121,#10000,11,1,11,1) +hasLocation(#20120,#20121) +enclosingStmt(#20120,#20108) +exprContainers(#20120,#20001) +literals("a","a",#20120) +bind(#20120,#20015) +isOptionalChaining(#20118) +#20122=* +exprs(#20122,14,#20116,1,"b?.().c?.().d") +#20123=@"loc,{#10000},11,7,11,19" +locations_default(#20123,#10000,11,7,11,19) +hasLocation(#20122,#20123) +enclosingStmt(#20122,#20108) +exprContainers(#20122,#20001) +#20124=* +exprs(#20124,13,#20122,0,"b?.().c?.()") +#20125=@"loc,{#10000},11,7,11,17" +locations_default(#20125,#10000,11,7,11,17) +hasLocation(#20124,#20125) +enclosingStmt(#20124,#20108) +exprContainers(#20124,#20001) +#20126=* +exprs(#20126,14,#20124,-1,"b?.().c") +#20127=@"loc,{#10000},11,7,11,13" +locations_default(#20127,#10000,11,7,11,13) +hasLocation(#20126,#20127) +enclosingStmt(#20126,#20108) +exprContainers(#20126,#20001) +#20128=* +exprs(#20128,13,#20126,0,"b?.()") +#20129=@"loc,{#10000},11,7,11,11" +locations_default(#20129,#10000,11,7,11,11) +hasLocation(#20128,#20129) +enclosingStmt(#20128,#20108) +exprContainers(#20128,#20001) +#20130=* +exprs(#20130,79,#20128,-1,"b") +#20131=@"loc,{#10000},11,7,11,7" +locations_default(#20131,#10000,11,7,11,7) +hasLocation(#20130,#20131) +enclosingStmt(#20130,#20108) +exprContainers(#20130,#20001) +literals("b","b",#20130) +bind(#20130,#20099) +isOptionalChaining(#20128) +#20132=* +exprs(#20132,0,#20126,1,"c") +#20133=@"loc,{#10000},11,13,11,13" +locations_default(#20133,#10000,11,13,11,13) +hasLocation(#20132,#20133) +enclosingStmt(#20132,#20108) +exprContainers(#20132,#20001) +literals("c","c",#20132) +isOptionalChaining(#20124) +#20134=* +exprs(#20134,0,#20122,1,"d") +#20135=@"loc,{#10000},11,19,11,19" +locations_default(#20135,#10000,11,19,11,19) +hasLocation(#20134,#20135) +enclosingStmt(#20134,#20108) +exprContainers(#20134,#20001) +literals("d","d",#20134) +#20136=* +exprs(#20136,0,#20114,1,"e") +#20137=@"loc,{#10000},11,22,11,22" +locations_default(#20137,#10000,11,22,11,22) +hasLocation(#20136,#20137) +enclosingStmt(#20136,#20108) +exprContainers(#20136,#20001) +literals("e","e",#20136) +isOptionalChaining(#20112) +#20138=* +exprs(#20138,0,#20110,1,"f") +#20139=@"loc,{#10000},11,28,11,28" +locations_default(#20139,#10000,11,28,11,28) +hasLocation(#20138,#20139) +enclosingStmt(#20138,#20108) +exprContainers(#20138,#20001) +literals("f","f",#20138) +#20140=* +stmts(#20140,3,#20001,6,"if (a?. ... alse;\n}") +#20141=@"loc,{#10000},13,1,17,1" +locations_default(#20141,#10000,13,1,17,1) +hasLocation(#20140,#20141) +stmtContainers(#20140,#20001) +#20142=* +exprs(#20142,14,#20140,0,"a?.b") +#20143=@"loc,{#10000},13,5,13,8" +locations_default(#20143,#10000,13,5,13,8) +hasLocation(#20142,#20143) +enclosingStmt(#20142,#20140) +exprContainers(#20142,#20001) +#20144=* +exprs(#20144,79,#20142,0,"a") +#20145=@"loc,{#10000},13,5,13,5" +locations_default(#20145,#10000,13,5,13,5) +hasLocation(#20144,#20145) +enclosingStmt(#20144,#20140) +exprContainers(#20144,#20001) +literals("a","a",#20144) +bind(#20144,#20015) +#20146=* +exprs(#20146,0,#20142,1,"b") +#20147=@"loc,{#10000},13,8,13,8" +locations_default(#20147,#10000,13,8,13,8) +hasLocation(#20146,#20147) +enclosingStmt(#20146,#20140) +exprContainers(#20146,#20001) +literals("b","b",#20146) +isOptionalChaining(#20142) +#20148=* +stmts(#20148,1,#20140,1,"{\n true;\n}") +#20149=@"loc,{#10000},13,11,15,1" +locations_default(#20149,#10000,13,11,15,1) +hasLocation(#20148,#20149) +stmtContainers(#20148,#20001) +#20150=* +stmts(#20150,2,#20148,0,"true;") +#20151=@"loc,{#10000},14,5,14,9" +locations_default(#20151,#10000,14,5,14,9) +hasLocation(#20150,#20151) +stmtContainers(#20150,#20001) +#20152=* +exprs(#20152,2,#20150,0,"true") +#20153=@"loc,{#10000},14,5,14,8" +locations_default(#20153,#10000,14,5,14,8) +hasLocation(#20152,#20153) +enclosingStmt(#20152,#20150) +exprContainers(#20152,#20001) +literals("true","true",#20152) +#20154=* +stmts(#20154,1,#20140,2,"{\n false;\n}") +#20155=@"loc,{#10000},15,8,17,1" +locations_default(#20155,#10000,15,8,17,1) +hasLocation(#20154,#20155) +stmtContainers(#20154,#20001) +#20156=* +stmts(#20156,2,#20154,0,"false;") +#20157=@"loc,{#10000},16,5,16,10" +locations_default(#20157,#10000,16,5,16,10) +hasLocation(#20156,#20157) +stmtContainers(#20156,#20001) +#20158=* +exprs(#20158,2,#20156,0,"false") +#20159=@"loc,{#10000},16,5,16,9" +locations_default(#20159,#10000,16,5,16,9) +hasLocation(#20158,#20159) +enclosingStmt(#20158,#20156) +exprContainers(#20158,#20001) +literals("false","false",#20158) +#20160=* +stmts(#20160,3,#20001,7,"if (!a? ... alse;\n}") +#20161=@"loc,{#10000},19,1,23,1" +locations_default(#20161,#10000,19,1,23,1) +hasLocation(#20160,#20161) +stmtContainers(#20160,#20001) +#20162=* +exprs(#20162,18,#20160,0,"!a?.b") +#20163=@"loc,{#10000},19,5,19,9" +locations_default(#20163,#10000,19,5,19,9) +hasLocation(#20162,#20163) +enclosingStmt(#20162,#20160) +exprContainers(#20162,#20001) +#20164=* +exprs(#20164,14,#20162,0,"a?.b") +#20165=@"loc,{#10000},19,6,19,9" +locations_default(#20165,#10000,19,6,19,9) +hasLocation(#20164,#20165) +enclosingStmt(#20164,#20160) +exprContainers(#20164,#20001) +#20166=* +exprs(#20166,79,#20164,0,"a") +#20167=@"loc,{#10000},19,6,19,6" +locations_default(#20167,#10000,19,6,19,6) +hasLocation(#20166,#20167) +enclosingStmt(#20166,#20160) +exprContainers(#20166,#20001) +literals("a","a",#20166) +bind(#20166,#20015) +#20168=* +exprs(#20168,0,#20164,1,"b") +#20169=@"loc,{#10000},19,9,19,9" +locations_default(#20169,#10000,19,9,19,9) +hasLocation(#20168,#20169) +enclosingStmt(#20168,#20160) +exprContainers(#20168,#20001) +literals("b","b",#20168) +isOptionalChaining(#20164) +#20170=* +stmts(#20170,1,#20160,1,"{\n true;\n}") +#20171=@"loc,{#10000},19,12,21,1" +locations_default(#20171,#10000,19,12,21,1) +hasLocation(#20170,#20171) +stmtContainers(#20170,#20001) +#20172=* +stmts(#20172,2,#20170,0,"true;") +#20173=@"loc,{#10000},20,5,20,9" +locations_default(#20173,#10000,20,5,20,9) +hasLocation(#20172,#20173) +stmtContainers(#20172,#20001) +#20174=* +exprs(#20174,2,#20172,0,"true") +#20175=@"loc,{#10000},20,5,20,8" +locations_default(#20175,#10000,20,5,20,8) +hasLocation(#20174,#20175) +enclosingStmt(#20174,#20172) +exprContainers(#20174,#20001) +literals("true","true",#20174) +#20176=* +stmts(#20176,1,#20160,2,"{\n false;\n}") +#20177=@"loc,{#10000},21,8,23,1" +locations_default(#20177,#10000,21,8,23,1) +hasLocation(#20176,#20177) +stmtContainers(#20176,#20001) +#20178=* +stmts(#20178,2,#20176,0,"false;") +#20179=@"loc,{#10000},22,5,22,10" +locations_default(#20179,#10000,22,5,22,10) +hasLocation(#20178,#20179) +stmtContainers(#20178,#20001) +#20180=* +exprs(#20180,2,#20178,0,"false") +#20181=@"loc,{#10000},22,5,22,9" +locations_default(#20181,#10000,22,5,22,9) +hasLocation(#20180,#20181) +enclosingStmt(#20180,#20178) +exprContainers(#20180,#20001) +literals("false","false",#20180) +#20182=* +stmts(#20182,3,#20001,8,"if (a?. ... alse;\n}") +#20183=@"loc,{#10000},25,1,29,1" +locations_default(#20183,#10000,25,1,29,1) +hasLocation(#20182,#20183) +stmtContainers(#20182,#20001) +#20184=* +exprs(#20184,44,#20182,0,"a?.b && c?.d") +#20185=@"loc,{#10000},25,5,25,16" +locations_default(#20185,#10000,25,5,25,16) +hasLocation(#20184,#20185) +enclosingStmt(#20184,#20182) +exprContainers(#20184,#20001) +#20186=* +exprs(#20186,14,#20184,0,"a?.b") +#20187=@"loc,{#10000},25,5,25,8" +locations_default(#20187,#10000,25,5,25,8) +hasLocation(#20186,#20187) +enclosingStmt(#20186,#20182) +exprContainers(#20186,#20001) +#20188=* +exprs(#20188,79,#20186,0,"a") +#20189=@"loc,{#10000},25,5,25,5" +locations_default(#20189,#10000,25,5,25,5) +hasLocation(#20188,#20189) +enclosingStmt(#20188,#20182) +exprContainers(#20188,#20001) +literals("a","a",#20188) +bind(#20188,#20015) +#20190=* +exprs(#20190,0,#20186,1,"b") +#20191=@"loc,{#10000},25,8,25,8" +locations_default(#20191,#10000,25,8,25,8) +hasLocation(#20190,#20191) +enclosingStmt(#20190,#20182) +exprContainers(#20190,#20001) +literals("b","b",#20190) +isOptionalChaining(#20186) +#20192=* +exprs(#20192,14,#20184,1,"c?.d") +#20193=@"loc,{#10000},25,13,25,16" +locations_default(#20193,#10000,25,13,25,16) +hasLocation(#20192,#20193) +enclosingStmt(#20192,#20182) +exprContainers(#20192,#20001) +#20194=* +exprs(#20194,79,#20192,0,"c") +#20195=@"loc,{#10000},25,13,25,13" +locations_default(#20195,#10000,25,13,25,13) +hasLocation(#20194,#20195) +enclosingStmt(#20194,#20182) +exprContainers(#20194,#20001) +literals("c","c",#20194) +#20196=@"var;{c};{#20000}" +variables(#20196,"c",#20000) +bind(#20194,#20196) +#20197=* +exprs(#20197,0,#20192,1,"d") +#20198=@"loc,{#10000},25,16,25,16" +locations_default(#20198,#10000,25,16,25,16) +hasLocation(#20197,#20198) +enclosingStmt(#20197,#20182) +exprContainers(#20197,#20001) +literals("d","d",#20197) +isOptionalChaining(#20192) +#20199=* +stmts(#20199,1,#20182,1,"{\n true;\n}") +#20200=@"loc,{#10000},25,19,27,1" +locations_default(#20200,#10000,25,19,27,1) +hasLocation(#20199,#20200) +stmtContainers(#20199,#20001) +#20201=* +stmts(#20201,2,#20199,0,"true;") +#20202=@"loc,{#10000},26,5,26,9" +locations_default(#20202,#10000,26,5,26,9) +hasLocation(#20201,#20202) +stmtContainers(#20201,#20001) +#20203=* +exprs(#20203,2,#20201,0,"true") +#20204=@"loc,{#10000},26,5,26,8" +locations_default(#20204,#10000,26,5,26,8) +hasLocation(#20203,#20204) +enclosingStmt(#20203,#20201) +exprContainers(#20203,#20001) +literals("true","true",#20203) +#20205=* +stmts(#20205,1,#20182,2,"{\n false;\n}") +#20206=@"loc,{#10000},27,8,29,1" +locations_default(#20206,#10000,27,8,29,1) +hasLocation(#20205,#20206) +stmtContainers(#20205,#20001) +#20207=* +stmts(#20207,2,#20205,0,"false;") +#20208=@"loc,{#10000},28,5,28,10" +locations_default(#20208,#10000,28,5,28,10) +hasLocation(#20207,#20208) +stmtContainers(#20207,#20001) +#20209=* +exprs(#20209,2,#20207,0,"false") +#20210=@"loc,{#10000},28,5,28,9" +locations_default(#20210,#10000,28,5,28,9) +hasLocation(#20209,#20210) +enclosingStmt(#20209,#20207) +exprContainers(#20209,#20001) +literals("false","false",#20209) +#20211=* +lines(#20211,#20001,"a?.b.c(++x).d;"," +") +hasLocation(#20211,#20004) +#20212=* +lines(#20212,#20001,""," +") +#20213=@"loc,{#10000},2,1,2,0" +locations_default(#20213,#10000,2,1,2,0) +hasLocation(#20212,#20213) +#20214=* +lines(#20214,#20001,"a?.b[3].c?.(x).d;"," +") +hasLocation(#20214,#20028) +#20215=* +lines(#20215,#20001,""," +") +#20216=@"loc,{#10000},4,1,4,0" +locations_default(#20216,#10000,4,1,4,0) +hasLocation(#20215,#20216) +#20217=* +lines(#20217,#20001,"(a?.b).c;"," +") +hasLocation(#20217,#20052) +#20218=* +lines(#20218,#20001,""," +") +#20219=@"loc,{#10000},6,1,6,0" +locations_default(#20219,#10000,6,1,6,0) +hasLocation(#20218,#20219) +#20220=* +lines(#20220,#20001,"(a?.b.c).d;"," +") +hasLocation(#20220,#20066) +#20221=* +lines(#20221,#20001,""," +") +#20222=@"loc,{#10000},8,1,8,0" +locations_default(#20222,#10000,8,1,8,0) +hasLocation(#20221,#20222) +#20223=* +lines(#20223,#20001,"a?.[b?.c?.d].e?.f;"," +") +hasLocation(#20223,#20084) +#20224=* +lines(#20224,#20001,""," +") +#20225=@"loc,{#10000},10,1,10,0" +locations_default(#20225,#10000,10,1,10,0) +hasLocation(#20224,#20225) +#20226=* +lines(#20226,#20001,"a?.()[b?.().c?.().d].e?.().f;"," +") +hasLocation(#20226,#20109) +#20227=* +lines(#20227,#20001,""," +") +#20228=@"loc,{#10000},12,1,12,0" +locations_default(#20228,#10000,12,1,12,0) +hasLocation(#20227,#20228) +#20229=* +lines(#20229,#20001,"if (a?.b) {"," +") +#20230=@"loc,{#10000},13,1,13,11" +locations_default(#20230,#10000,13,1,13,11) +hasLocation(#20229,#20230) +#20231=* +lines(#20231,#20001," true;"," +") +#20232=@"loc,{#10000},14,1,14,9" +locations_default(#20232,#10000,14,1,14,9) +hasLocation(#20231,#20232) +indentation(#10000,14," ",4) +#20233=* +lines(#20233,#20001,"} else {"," +") +#20234=@"loc,{#10000},15,1,15,8" +locations_default(#20234,#10000,15,1,15,8) +hasLocation(#20233,#20234) +#20235=* +lines(#20235,#20001," false;"," +") +#20236=@"loc,{#10000},16,1,16,10" +locations_default(#20236,#10000,16,1,16,10) +hasLocation(#20235,#20236) +indentation(#10000,16," ",4) +#20237=* +lines(#20237,#20001,"}"," +") +#20238=@"loc,{#10000},17,1,17,1" +locations_default(#20238,#10000,17,1,17,1) +hasLocation(#20237,#20238) +#20239=* +lines(#20239,#20001,""," +") +#20240=@"loc,{#10000},18,1,18,0" +locations_default(#20240,#10000,18,1,18,0) +hasLocation(#20239,#20240) +#20241=* +lines(#20241,#20001,"if (!a?.b) {"," +") +#20242=@"loc,{#10000},19,1,19,12" +locations_default(#20242,#10000,19,1,19,12) +hasLocation(#20241,#20242) +#20243=* +lines(#20243,#20001," true;"," +") +#20244=@"loc,{#10000},20,1,20,9" +locations_default(#20244,#10000,20,1,20,9) +hasLocation(#20243,#20244) +indentation(#10000,20," ",4) +#20245=* +lines(#20245,#20001,"} else {"," +") +#20246=@"loc,{#10000},21,1,21,8" +locations_default(#20246,#10000,21,1,21,8) +hasLocation(#20245,#20246) +#20247=* +lines(#20247,#20001," false;"," +") +#20248=@"loc,{#10000},22,1,22,10" +locations_default(#20248,#10000,22,1,22,10) +hasLocation(#20247,#20248) +indentation(#10000,22," ",4) +#20249=* +lines(#20249,#20001,"}"," +") +#20250=@"loc,{#10000},23,1,23,1" +locations_default(#20250,#10000,23,1,23,1) +hasLocation(#20249,#20250) +#20251=* +lines(#20251,#20001,""," +") +#20252=@"loc,{#10000},24,1,24,0" +locations_default(#20252,#10000,24,1,24,0) +hasLocation(#20251,#20252) +#20253=* +lines(#20253,#20001,"if (a?.b && c?.d) {"," +") +#20254=@"loc,{#10000},25,1,25,19" +locations_default(#20254,#10000,25,1,25,19) +hasLocation(#20253,#20254) +#20255=* +lines(#20255,#20001," true;"," +") +#20256=@"loc,{#10000},26,1,26,9" +locations_default(#20256,#10000,26,1,26,9) +hasLocation(#20255,#20256) +indentation(#10000,26," ",4) +#20257=* +lines(#20257,#20001,"} else {"," +") +#20258=@"loc,{#10000},27,1,27,8" +locations_default(#20258,#10000,27,1,27,8) +hasLocation(#20257,#20258) +#20259=* +lines(#20259,#20001," false;"," +") +#20260=@"loc,{#10000},28,1,28,10" +locations_default(#20260,#10000,28,1,28,10) +hasLocation(#20259,#20260) +indentation(#10000,28," ",4) +#20261=* +lines(#20261,#20001,"}"," +") +#20262=@"loc,{#10000},29,1,29,1" +locations_default(#20262,#10000,29,1,29,1) +hasLocation(#20261,#20262) +numlines(#20001,29,21,0) +#20263=* +tokeninfo(#20263,6,#20001,0,"a") +hasLocation(#20263,#20014) +#20264=* +tokeninfo(#20264,8,#20001,1,"?.") +#20265=@"loc,{#10000},1,2,1,3" +locations_default(#20265,#10000,1,2,1,3) +hasLocation(#20264,#20265) +#20266=* +tokeninfo(#20266,6,#20001,2,"b") +hasLocation(#20266,#20017) +#20267=* +tokeninfo(#20267,8,#20001,3,".") +#20268=@"loc,{#10000},1,5,1,5" +locations_default(#20268,#10000,1,5,1,5) +hasLocation(#20267,#20268) +#20269=* +tokeninfo(#20269,6,#20001,4,"c") +hasLocation(#20269,#20019) +#20270=* +tokeninfo(#20270,8,#20001,5,"(") +#20271=@"loc,{#10000},1,7,1,7" +locations_default(#20271,#10000,1,7,1,7) +hasLocation(#20270,#20271) +#20272=* +tokeninfo(#20272,8,#20001,6,"++") +#20273=@"loc,{#10000},1,8,1,9" +locations_default(#20273,#10000,1,8,1,9) +hasLocation(#20272,#20273) +#20274=* +tokeninfo(#20274,6,#20001,7,"x") +hasLocation(#20274,#20023) +#20275=* +tokeninfo(#20275,8,#20001,8,")") +#20276=@"loc,{#10000},1,11,1,11" +locations_default(#20276,#10000,1,11,1,11) +hasLocation(#20275,#20276) +#20277=* +tokeninfo(#20277,8,#20001,9,".") +#20278=@"loc,{#10000},1,12,1,12" +locations_default(#20278,#10000,1,12,1,12) +hasLocation(#20277,#20278) +#20279=* +tokeninfo(#20279,6,#20001,10,"d") +hasLocation(#20279,#20026) +#20280=* +tokeninfo(#20280,8,#20001,11,";") +#20281=@"loc,{#10000},1,14,1,14" +locations_default(#20281,#10000,1,14,1,14) +hasLocation(#20280,#20281) +#20282=* +tokeninfo(#20282,6,#20001,12,"a") +hasLocation(#20282,#20040) +#20283=* +tokeninfo(#20283,8,#20001,13,"?.") +#20284=@"loc,{#10000},3,2,3,3" +locations_default(#20284,#10000,3,2,3,3) +hasLocation(#20283,#20284) +#20285=* +tokeninfo(#20285,6,#20001,14,"b") +hasLocation(#20285,#20042) +#20286=* +tokeninfo(#20286,8,#20001,15,"[") +#20287=@"loc,{#10000},3,5,3,5" +locations_default(#20287,#10000,3,5,3,5) +hasLocation(#20286,#20287) +#20288=* +tokeninfo(#20288,3,#20001,16,"3") +hasLocation(#20288,#20044) +#20289=* +tokeninfo(#20289,8,#20001,17,"]") +#20290=@"loc,{#10000},3,7,3,7" +locations_default(#20290,#10000,3,7,3,7) +hasLocation(#20289,#20290) +#20291=* +tokeninfo(#20291,8,#20001,18,".") +#20292=@"loc,{#10000},3,8,3,8" +locations_default(#20292,#10000,3,8,3,8) +hasLocation(#20291,#20292) +#20293=* +tokeninfo(#20293,6,#20001,19,"c") +hasLocation(#20293,#20046) +#20294=* +tokeninfo(#20294,8,#20001,20,"?.") +#20295=@"loc,{#10000},3,10,3,11" +locations_default(#20295,#10000,3,10,3,11) +hasLocation(#20294,#20295) +#20296=* +tokeninfo(#20296,8,#20001,21,"(") +#20297=@"loc,{#10000},3,12,3,12" +locations_default(#20297,#10000,3,12,3,12) +hasLocation(#20296,#20297) +#20298=* +tokeninfo(#20298,6,#20001,22,"x") +hasLocation(#20298,#20048) +#20299=* +tokeninfo(#20299,8,#20001,23,")") +#20300=@"loc,{#10000},3,14,3,14" +locations_default(#20300,#10000,3,14,3,14) +hasLocation(#20299,#20300) +#20301=* +tokeninfo(#20301,8,#20001,24,".") +#20302=@"loc,{#10000},3,15,3,15" +locations_default(#20302,#10000,3,15,3,15) +hasLocation(#20301,#20302) +#20303=* +tokeninfo(#20303,6,#20001,25,"d") +hasLocation(#20303,#20050) +#20304=* +tokeninfo(#20304,8,#20001,26,";") +#20305=@"loc,{#10000},3,17,3,17" +locations_default(#20305,#10000,3,17,3,17) +hasLocation(#20304,#20305) +#20306=* +tokeninfo(#20306,8,#20001,27,"(") +#20307=@"loc,{#10000},5,1,5,1" +locations_default(#20307,#10000,5,1,5,1) +hasLocation(#20306,#20307) +#20308=* +tokeninfo(#20308,6,#20001,28,"a") +hasLocation(#20308,#20060) +#20309=* +tokeninfo(#20309,8,#20001,29,"?.") +#20310=@"loc,{#10000},5,3,5,4" +locations_default(#20310,#10000,5,3,5,4) +hasLocation(#20309,#20310) +#20311=* +tokeninfo(#20311,6,#20001,30,"b") +hasLocation(#20311,#20062) +#20312=* +tokeninfo(#20312,8,#20001,31,")") +#20313=@"loc,{#10000},5,6,5,6" +locations_default(#20313,#10000,5,6,5,6) +hasLocation(#20312,#20313) +#20314=* +tokeninfo(#20314,8,#20001,32,".") +#20315=@"loc,{#10000},5,7,5,7" +locations_default(#20315,#10000,5,7,5,7) +hasLocation(#20314,#20315) +#20316=* +tokeninfo(#20316,6,#20001,33,"c") +hasLocation(#20316,#20064) +#20317=* +tokeninfo(#20317,8,#20001,34,";") +#20318=@"loc,{#10000},5,9,5,9" +locations_default(#20318,#10000,5,9,5,9) +hasLocation(#20317,#20318) +#20319=* +tokeninfo(#20319,8,#20001,35,"(") +#20320=@"loc,{#10000},7,1,7,1" +locations_default(#20320,#10000,7,1,7,1) +hasLocation(#20319,#20320) +#20321=* +tokeninfo(#20321,6,#20001,36,"a") +hasLocation(#20321,#20076) +#20322=* +tokeninfo(#20322,8,#20001,37,"?.") +#20323=@"loc,{#10000},7,3,7,4" +locations_default(#20323,#10000,7,3,7,4) +hasLocation(#20322,#20323) +#20324=* +tokeninfo(#20324,6,#20001,38,"b") +hasLocation(#20324,#20078) +#20325=* +tokeninfo(#20325,8,#20001,39,".") +#20326=@"loc,{#10000},7,6,7,6" +locations_default(#20326,#10000,7,6,7,6) +hasLocation(#20325,#20326) +#20327=* +tokeninfo(#20327,6,#20001,40,"c") +hasLocation(#20327,#20080) +#20328=* +tokeninfo(#20328,8,#20001,41,")") +#20329=@"loc,{#10000},7,8,7,8" +locations_default(#20329,#10000,7,8,7,8) +hasLocation(#20328,#20329) +#20330=* +tokeninfo(#20330,8,#20001,42,".") +#20331=@"loc,{#10000},7,9,7,9" +locations_default(#20331,#10000,7,9,7,9) +hasLocation(#20330,#20331) +#20332=* +tokeninfo(#20332,6,#20001,43,"d") +hasLocation(#20332,#20082) +#20333=* +tokeninfo(#20333,8,#20001,44,";") +#20334=@"loc,{#10000},7,11,7,11" +locations_default(#20334,#10000,7,11,7,11) +hasLocation(#20333,#20334) +#20335=* +tokeninfo(#20335,6,#20001,45,"a") +hasLocation(#20335,#20092) +#20336=* +tokeninfo(#20336,8,#20001,46,"?.") +#20337=@"loc,{#10000},9,2,9,3" +locations_default(#20337,#10000,9,2,9,3) +hasLocation(#20336,#20337) +#20338=* +tokeninfo(#20338,8,#20001,47,"[") +#20339=@"loc,{#10000},9,4,9,4" +locations_default(#20339,#10000,9,4,9,4) +hasLocation(#20338,#20339) +#20340=* +tokeninfo(#20340,6,#20001,48,"b") +hasLocation(#20340,#20098) +#20341=* +tokeninfo(#20341,8,#20001,49,"?.") +#20342=@"loc,{#10000},9,6,9,7" +locations_default(#20342,#10000,9,6,9,7) +hasLocation(#20341,#20342) +#20343=* +tokeninfo(#20343,6,#20001,50,"c") +hasLocation(#20343,#20101) +#20344=* +tokeninfo(#20344,8,#20001,51,"?.") +#20345=@"loc,{#10000},9,9,9,10" +locations_default(#20345,#10000,9,9,9,10) +hasLocation(#20344,#20345) +#20346=* +tokeninfo(#20346,6,#20001,52,"d") +hasLocation(#20346,#20103) +#20347=* +tokeninfo(#20347,8,#20001,53,"]") +#20348=@"loc,{#10000},9,12,9,12" +locations_default(#20348,#10000,9,12,9,12) +hasLocation(#20347,#20348) +#20349=* +tokeninfo(#20349,8,#20001,54,".") +#20350=@"loc,{#10000},9,13,9,13" +locations_default(#20350,#10000,9,13,9,13) +hasLocation(#20349,#20350) +#20351=* +tokeninfo(#20351,6,#20001,55,"e") +hasLocation(#20351,#20105) +#20352=* +tokeninfo(#20352,8,#20001,56,"?.") +#20353=@"loc,{#10000},9,15,9,16" +locations_default(#20353,#10000,9,15,9,16) +hasLocation(#20352,#20353) +#20354=* +tokeninfo(#20354,6,#20001,57,"f") +hasLocation(#20354,#20107) +#20355=* +tokeninfo(#20355,8,#20001,58,";") +#20356=@"loc,{#10000},9,18,9,18" +locations_default(#20356,#10000,9,18,9,18) +hasLocation(#20355,#20356) +#20357=* +tokeninfo(#20357,6,#20001,59,"a") +hasLocation(#20357,#20121) +#20358=* +tokeninfo(#20358,8,#20001,60,"?.") +#20359=@"loc,{#10000},11,2,11,3" +locations_default(#20359,#10000,11,2,11,3) +hasLocation(#20358,#20359) +#20360=* +tokeninfo(#20360,8,#20001,61,"(") +#20361=@"loc,{#10000},11,4,11,4" +locations_default(#20361,#10000,11,4,11,4) +hasLocation(#20360,#20361) +#20362=* +tokeninfo(#20362,8,#20001,62,")") +#20363=@"loc,{#10000},11,5,11,5" +locations_default(#20363,#10000,11,5,11,5) +hasLocation(#20362,#20363) +#20364=* +tokeninfo(#20364,8,#20001,63,"[") +#20365=@"loc,{#10000},11,6,11,6" +locations_default(#20365,#10000,11,6,11,6) +hasLocation(#20364,#20365) +#20366=* +tokeninfo(#20366,6,#20001,64,"b") +hasLocation(#20366,#20131) +#20367=* +tokeninfo(#20367,8,#20001,65,"?.") +#20368=@"loc,{#10000},11,8,11,9" +locations_default(#20368,#10000,11,8,11,9) +hasLocation(#20367,#20368) +#20369=* +tokeninfo(#20369,8,#20001,66,"(") +#20370=@"loc,{#10000},11,10,11,10" +locations_default(#20370,#10000,11,10,11,10) +hasLocation(#20369,#20370) +#20371=* +tokeninfo(#20371,8,#20001,67,")") +#20372=@"loc,{#10000},11,11,11,11" +locations_default(#20372,#10000,11,11,11,11) +hasLocation(#20371,#20372) +#20373=* +tokeninfo(#20373,8,#20001,68,".") +#20374=@"loc,{#10000},11,12,11,12" +locations_default(#20374,#10000,11,12,11,12) +hasLocation(#20373,#20374) +#20375=* +tokeninfo(#20375,6,#20001,69,"c") +hasLocation(#20375,#20133) +#20376=* +tokeninfo(#20376,8,#20001,70,"?.") +#20377=@"loc,{#10000},11,14,11,15" +locations_default(#20377,#10000,11,14,11,15) +hasLocation(#20376,#20377) +#20378=* +tokeninfo(#20378,8,#20001,71,"(") +#20379=@"loc,{#10000},11,16,11,16" +locations_default(#20379,#10000,11,16,11,16) +hasLocation(#20378,#20379) +#20380=* +tokeninfo(#20380,8,#20001,72,")") +#20381=@"loc,{#10000},11,17,11,17" +locations_default(#20381,#10000,11,17,11,17) +hasLocation(#20380,#20381) +#20382=* +tokeninfo(#20382,8,#20001,73,".") +#20383=@"loc,{#10000},11,18,11,18" +locations_default(#20383,#10000,11,18,11,18) +hasLocation(#20382,#20383) +#20384=* +tokeninfo(#20384,6,#20001,74,"d") +hasLocation(#20384,#20135) +#20385=* +tokeninfo(#20385,8,#20001,75,"]") +#20386=@"loc,{#10000},11,20,11,20" +locations_default(#20386,#10000,11,20,11,20) +hasLocation(#20385,#20386) +#20387=* +tokeninfo(#20387,8,#20001,76,".") +#20388=@"loc,{#10000},11,21,11,21" +locations_default(#20388,#10000,11,21,11,21) +hasLocation(#20387,#20388) +#20389=* +tokeninfo(#20389,6,#20001,77,"e") +hasLocation(#20389,#20137) +#20390=* +tokeninfo(#20390,8,#20001,78,"?.") +#20391=@"loc,{#10000},11,23,11,24" +locations_default(#20391,#10000,11,23,11,24) +hasLocation(#20390,#20391) +#20392=* +tokeninfo(#20392,8,#20001,79,"(") +#20393=@"loc,{#10000},11,25,11,25" +locations_default(#20393,#10000,11,25,11,25) +hasLocation(#20392,#20393) +#20394=* +tokeninfo(#20394,8,#20001,80,")") +#20395=@"loc,{#10000},11,26,11,26" +locations_default(#20395,#10000,11,26,11,26) +hasLocation(#20394,#20395) +#20396=* +tokeninfo(#20396,8,#20001,81,".") +#20397=@"loc,{#10000},11,27,11,27" +locations_default(#20397,#10000,11,27,11,27) +hasLocation(#20396,#20397) +#20398=* +tokeninfo(#20398,6,#20001,82,"f") +hasLocation(#20398,#20139) +#20399=* +tokeninfo(#20399,8,#20001,83,";") +#20400=@"loc,{#10000},11,29,11,29" +locations_default(#20400,#10000,11,29,11,29) +hasLocation(#20399,#20400) +#20401=* +tokeninfo(#20401,7,#20001,84,"if") +#20402=@"loc,{#10000},13,1,13,2" +locations_default(#20402,#10000,13,1,13,2) +hasLocation(#20401,#20402) +#20403=* +tokeninfo(#20403,8,#20001,85,"(") +#20404=@"loc,{#10000},13,4,13,4" +locations_default(#20404,#10000,13,4,13,4) +hasLocation(#20403,#20404) +#20405=* +tokeninfo(#20405,6,#20001,86,"a") +hasLocation(#20405,#20145) +#20406=* +tokeninfo(#20406,8,#20001,87,"?.") +#20407=@"loc,{#10000},13,6,13,7" +locations_default(#20407,#10000,13,6,13,7) +hasLocation(#20406,#20407) +#20408=* +tokeninfo(#20408,6,#20001,88,"b") +hasLocation(#20408,#20147) +#20409=* +tokeninfo(#20409,8,#20001,89,")") +#20410=@"loc,{#10000},13,9,13,9" +locations_default(#20410,#10000,13,9,13,9) +hasLocation(#20409,#20410) +#20411=* +tokeninfo(#20411,8,#20001,90,"{") +#20412=@"loc,{#10000},13,11,13,11" +locations_default(#20412,#10000,13,11,13,11) +hasLocation(#20411,#20412) +#20413=* +tokeninfo(#20413,2,#20001,91,"true") +hasLocation(#20413,#20153) +#20414=* +tokeninfo(#20414,8,#20001,92,";") +#20415=@"loc,{#10000},14,9,14,9" +locations_default(#20415,#10000,14,9,14,9) +hasLocation(#20414,#20415) +#20416=* +tokeninfo(#20416,8,#20001,93,"}") +#20417=@"loc,{#10000},15,1,15,1" +locations_default(#20417,#10000,15,1,15,1) +hasLocation(#20416,#20417) +#20418=* +tokeninfo(#20418,7,#20001,94,"else") +#20419=@"loc,{#10000},15,3,15,6" +locations_default(#20419,#10000,15,3,15,6) +hasLocation(#20418,#20419) +#20420=* +tokeninfo(#20420,8,#20001,95,"{") +#20421=@"loc,{#10000},15,8,15,8" +locations_default(#20421,#10000,15,8,15,8) +hasLocation(#20420,#20421) +#20422=* +tokeninfo(#20422,2,#20001,96,"false") +hasLocation(#20422,#20159) +#20423=* +tokeninfo(#20423,8,#20001,97,";") +#20424=@"loc,{#10000},16,10,16,10" +locations_default(#20424,#10000,16,10,16,10) +hasLocation(#20423,#20424) +#20425=* +tokeninfo(#20425,8,#20001,98,"}") +hasLocation(#20425,#20238) +#20426=* +tokeninfo(#20426,7,#20001,99,"if") +#20427=@"loc,{#10000},19,1,19,2" +locations_default(#20427,#10000,19,1,19,2) +hasLocation(#20426,#20427) +#20428=* +tokeninfo(#20428,8,#20001,100,"(") +#20429=@"loc,{#10000},19,4,19,4" +locations_default(#20429,#10000,19,4,19,4) +hasLocation(#20428,#20429) +#20430=* +tokeninfo(#20430,8,#20001,101,"!") +#20431=@"loc,{#10000},19,5,19,5" +locations_default(#20431,#10000,19,5,19,5) +hasLocation(#20430,#20431) +#20432=* +tokeninfo(#20432,6,#20001,102,"a") +hasLocation(#20432,#20167) +#20433=* +tokeninfo(#20433,8,#20001,103,"?.") +#20434=@"loc,{#10000},19,7,19,8" +locations_default(#20434,#10000,19,7,19,8) +hasLocation(#20433,#20434) +#20435=* +tokeninfo(#20435,6,#20001,104,"b") +hasLocation(#20435,#20169) +#20436=* +tokeninfo(#20436,8,#20001,105,")") +#20437=@"loc,{#10000},19,10,19,10" +locations_default(#20437,#10000,19,10,19,10) +hasLocation(#20436,#20437) +#20438=* +tokeninfo(#20438,8,#20001,106,"{") +#20439=@"loc,{#10000},19,12,19,12" +locations_default(#20439,#10000,19,12,19,12) +hasLocation(#20438,#20439) +#20440=* +tokeninfo(#20440,2,#20001,107,"true") +hasLocation(#20440,#20175) +#20441=* +tokeninfo(#20441,8,#20001,108,";") +#20442=@"loc,{#10000},20,9,20,9" +locations_default(#20442,#10000,20,9,20,9) +hasLocation(#20441,#20442) +#20443=* +tokeninfo(#20443,8,#20001,109,"}") +#20444=@"loc,{#10000},21,1,21,1" +locations_default(#20444,#10000,21,1,21,1) +hasLocation(#20443,#20444) +#20445=* +tokeninfo(#20445,7,#20001,110,"else") +#20446=@"loc,{#10000},21,3,21,6" +locations_default(#20446,#10000,21,3,21,6) +hasLocation(#20445,#20446) +#20447=* +tokeninfo(#20447,8,#20001,111,"{") +#20448=@"loc,{#10000},21,8,21,8" +locations_default(#20448,#10000,21,8,21,8) +hasLocation(#20447,#20448) +#20449=* +tokeninfo(#20449,2,#20001,112,"false") +hasLocation(#20449,#20181) +#20450=* +tokeninfo(#20450,8,#20001,113,";") +#20451=@"loc,{#10000},22,10,22,10" +locations_default(#20451,#10000,22,10,22,10) +hasLocation(#20450,#20451) +#20452=* +tokeninfo(#20452,8,#20001,114,"}") +hasLocation(#20452,#20250) +#20453=* +tokeninfo(#20453,7,#20001,115,"if") +#20454=@"loc,{#10000},25,1,25,2" +locations_default(#20454,#10000,25,1,25,2) +hasLocation(#20453,#20454) +#20455=* +tokeninfo(#20455,8,#20001,116,"(") +#20456=@"loc,{#10000},25,4,25,4" +locations_default(#20456,#10000,25,4,25,4) +hasLocation(#20455,#20456) +#20457=* +tokeninfo(#20457,6,#20001,117,"a") +hasLocation(#20457,#20189) +#20458=* +tokeninfo(#20458,8,#20001,118,"?.") +#20459=@"loc,{#10000},25,6,25,7" +locations_default(#20459,#10000,25,6,25,7) +hasLocation(#20458,#20459) +#20460=* +tokeninfo(#20460,6,#20001,119,"b") +hasLocation(#20460,#20191) +#20461=* +tokeninfo(#20461,8,#20001,120,"&&") +#20462=@"loc,{#10000},25,10,25,11" +locations_default(#20462,#10000,25,10,25,11) +hasLocation(#20461,#20462) +#20463=* +tokeninfo(#20463,6,#20001,121,"c") +hasLocation(#20463,#20195) +#20464=* +tokeninfo(#20464,8,#20001,122,"?.") +#20465=@"loc,{#10000},25,14,25,15" +locations_default(#20465,#10000,25,14,25,15) +hasLocation(#20464,#20465) +#20466=* +tokeninfo(#20466,6,#20001,123,"d") +hasLocation(#20466,#20198) +#20467=* +tokeninfo(#20467,8,#20001,124,")") +#20468=@"loc,{#10000},25,17,25,17" +locations_default(#20468,#10000,25,17,25,17) +hasLocation(#20467,#20468) +#20469=* +tokeninfo(#20469,8,#20001,125,"{") +#20470=@"loc,{#10000},25,19,25,19" +locations_default(#20470,#10000,25,19,25,19) +hasLocation(#20469,#20470) +#20471=* +tokeninfo(#20471,2,#20001,126,"true") +hasLocation(#20471,#20204) +#20472=* +tokeninfo(#20472,8,#20001,127,";") +#20473=@"loc,{#10000},26,9,26,9" +locations_default(#20473,#10000,26,9,26,9) +hasLocation(#20472,#20473) +#20474=* +tokeninfo(#20474,8,#20001,128,"}") +#20475=@"loc,{#10000},27,1,27,1" +locations_default(#20475,#10000,27,1,27,1) +hasLocation(#20474,#20475) +#20476=* +tokeninfo(#20476,7,#20001,129,"else") +#20477=@"loc,{#10000},27,3,27,6" +locations_default(#20477,#10000,27,3,27,6) +hasLocation(#20476,#20477) +#20478=* +tokeninfo(#20478,8,#20001,130,"{") +#20479=@"loc,{#10000},27,8,27,8" +locations_default(#20479,#10000,27,8,27,8) +hasLocation(#20478,#20479) +#20480=* +tokeninfo(#20480,2,#20001,131,"false") +hasLocation(#20480,#20210) +#20481=* +tokeninfo(#20481,8,#20001,132,";") +#20482=@"loc,{#10000},28,10,28,10" +locations_default(#20482,#10000,28,10,28,10) +hasLocation(#20481,#20482) +#20483=* +tokeninfo(#20483,8,#20001,133,"}") +hasLocation(#20483,#20262) +#20484=* +tokeninfo(#20484,0,#20001,134,"") +#20485=@"loc,{#10000},30,1,30,0" +locations_default(#20485,#10000,30,1,30,0) +hasLocation(#20484,#20485) +#20486=* +entry_cfg_node(#20486,#20001) +#20487=@"loc,{#10000},1,1,1,0" +locations_default(#20487,#10000,1,1,1,0) +hasLocation(#20486,#20487) +#20488=* +exit_cfg_node(#20488,#20001) +hasLocation(#20488,#20485) +successor(#20182,#20184) +successor(#20184,#20188) +successor(#20190,#20186) +successor(#20188,#20190) +#20489=* +guard_node(#20489,1,#20186) +hasLocation(#20489,#20187) +successor(#20489,#20194) +#20490=* +guard_node(#20490,0,#20186) +hasLocation(#20490,#20187) +successor(#20490,#20205) +successor(#20186,#20489) +successor(#20186,#20490) +successor(#20188,#20205) +successor(#20197,#20192) +successor(#20194,#20197) +#20491=* +guard_node(#20491,1,#20192) +hasLocation(#20491,#20193) +successor(#20491,#20199) +#20492=* +guard_node(#20492,0,#20192) +hasLocation(#20492,#20193) +successor(#20492,#20205) +successor(#20192,#20491) +successor(#20192,#20492) +successor(#20194,#20205) +successor(#20199,#20201) +successor(#20201,#20203) +successor(#20203,#20488) +successor(#20205,#20207) +successor(#20207,#20209) +successor(#20209,#20488) +successor(#20160,#20166) +successor(#20168,#20164) +successor(#20166,#20168) +successor(#20164,#20162) +successor(#20166,#20162) +#20493=* +guard_node(#20493,0,#20164) +hasLocation(#20493,#20165) +successor(#20493,#20170) +#20494=* +guard_node(#20494,1,#20164) +hasLocation(#20494,#20165) +successor(#20494,#20176) +successor(#20162,#20493) +successor(#20162,#20494) +successor(#20170,#20172) +successor(#20172,#20174) +successor(#20174,#20182) +successor(#20176,#20178) +successor(#20178,#20180) +successor(#20180,#20182) +successor(#20140,#20144) +successor(#20146,#20142) +successor(#20144,#20146) +#20495=* +guard_node(#20495,1,#20142) +hasLocation(#20495,#20143) +successor(#20495,#20148) +#20496=* +guard_node(#20496,0,#20142) +hasLocation(#20496,#20143) +successor(#20496,#20154) +successor(#20142,#20495) +successor(#20142,#20496) +successor(#20144,#20154) +successor(#20148,#20150) +successor(#20150,#20152) +successor(#20152,#20160) +successor(#20154,#20156) +successor(#20156,#20158) +successor(#20158,#20160) +successor(#20108,#20120) +successor(#20138,#20110) +successor(#20136,#20114) +successor(#20134,#20122) +successor(#20132,#20126) +successor(#20130,#20128) +successor(#20128,#20132) +successor(#20130,#20116) +successor(#20126,#20124) +successor(#20124,#20134) +successor(#20126,#20116) +successor(#20122,#20116) +successor(#20120,#20118) +successor(#20118,#20130) +successor(#20120,#20140) +successor(#20116,#20136) +successor(#20114,#20112) +successor(#20112,#20138) +successor(#20114,#20140) +successor(#20110,#20140) +successor(#20083,#20091) +successor(#20106,#20085) +successor(#20104,#20087) +successor(#20102,#20093) +successor(#20100,#20095) +successor(#20097,#20100) +successor(#20095,#20102) +successor(#20097,#20089) +successor(#20093,#20089) +successor(#20095,#20089) +successor(#20091,#20097) +successor(#20089,#20104) +successor(#20091,#20108) +successor(#20087,#20106) +successor(#20085,#20108) +successor(#20087,#20108) +successor(#20065,#20069) +successor(#20081,#20067) +successor(#20069,#20075) +successor(#20079,#20071) +successor(#20077,#20073) +successor(#20075,#20077) +successor(#20073,#20079) +successor(#20075,#20081) +successor(#20071,#20081) +successor(#20067,#20083) +successor(#20051,#20055) +successor(#20063,#20053) +successor(#20055,#20059) +successor(#20061,#20057) +successor(#20059,#20061) +successor(#20057,#20063) +successor(#20059,#20063) +successor(#20053,#20065) +successor(#20027,#20039) +successor(#20049,#20029) +successor(#20047,#20031) +successor(#20045,#20033) +successor(#20043,#20035) +successor(#20041,#20037) +successor(#20039,#20041) +successor(#20037,#20043) +successor(#20039,#20051) +successor(#20035,#20045) +successor(#20033,#20047) +successor(#20031,#20049) +successor(#20033,#20051) +successor(#20029,#20051) +successor(#20003,#20013) +successor(#20025,#20005) +successor(#20022,#20020) +successor(#20020,#20007) +successor(#20018,#20009) +successor(#20016,#20011) +successor(#20013,#20016) +successor(#20011,#20018) +successor(#20013,#20027) +successor(#20009,#20022) +successor(#20007,#20025) +successor(#20005,#20027) +successor(#20486,#20003) +numlines(#10000,29,21,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/flow/input/array-types.js b/javascript/extractor/tests/flow/input/array-types.js new file mode 100644 index 000000000000..971986a1c107 --- /dev/null +++ b/javascript/extractor/tests/flow/input/array-types.js @@ -0,0 +1,4 @@ +function f(p: T) {} +function f(p: T[]) {} +function f(p: T[][]) {} +function f(p: T[][][]) {} diff --git a/javascript/extractor/tests/flow/input/declared-module-imports.js b/javascript/extractor/tests/flow/input/declared-module-imports.js new file mode 100644 index 000000000000..fc47c901ee79 --- /dev/null +++ b/javascript/extractor/tests/flow/input/declared-module-imports.js @@ -0,0 +1,4 @@ +declare module "m1" { + import type t from "m2"; + import typeof t from "m3"; +} diff --git a/javascript/extractor/tests/flow/input/get-set-methods.js b/javascript/extractor/tests/flow/input/get-set-methods.js new file mode 100644 index 000000000000..82d6a978d5f3 --- /dev/null +++ b/javascript/extractor/tests/flow/input/get-set-methods.js @@ -0,0 +1,4 @@ +class C { + get(v: T) { } + set(v: T) { } +} diff --git a/javascript/extractor/tests/flow/input/predicate-function-annotation.js b/javascript/extractor/tests/flow/input/predicate-function-annotation.js new file mode 100644 index 000000000000..8cc55ae2cc31 --- /dev/null +++ b/javascript/extractor/tests/flow/input/predicate-function-annotation.js @@ -0,0 +1,9 @@ +function f(a): boolean %checks { + return a; +} +function g(): %checks {} { + return b; +} + +(c): boolean %checks => c; +(d): %checks => d; diff --git a/javascript/extractor/tests/flow/output/trap/array-types.js.trap b/javascript/extractor/tests/flow/output/trap/array-types.js.trap new file mode 100644 index 000000000000..186ec124a96c --- /dev/null +++ b/javascript/extractor/tests/flow/output/trap/array-types.js.trap @@ -0,0 +1,492 @@ +#10000=@"/array-types.js;sourcefile" +files(#10000,"/array-types.js","array-types","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,5,0" +locations_default(#20002,#10000,1,1,5,0) +hasLocation(#20001,#20002) +#20003=@"var;{f};{#20000}" +variables(#20003,"f",#20000) +#20004=* +stmts(#20004,17,#20001,0,"function f(p: T) {}") +#20005=@"loc,{#10000},1,1,1,19" +locations_default(#20005,#10000,1,1,1,19) +hasLocation(#20004,#20005) +stmtContainers(#20004,#20001) +#20006=* +exprs(#20006,78,#20004,-1,"f") +#20007=@"loc,{#10000},1,10,1,10" +locations_default(#20007,#10000,1,10,1,10) +hasLocation(#20006,#20007) +exprContainers(#20006,#20004) +literals("f","f",#20006) +decl(#20006,#20003) +#20008=* +scopes(#20008,1) +scopenodes(#20004,#20008) +scopenesting(#20008,#20000) +#20009=@"var;{p};{#20008}" +variables(#20009,"p",#20008) +#20010=* +exprs(#20010,78,#20004,0,"p: T") +#20011=@"loc,{#10000},1,12,1,15" +locations_default(#20011,#10000,1,12,1,15) +hasLocation(#20010,#20011) +exprContainers(#20010,#20004) +literals("p","p",#20010) +decl(#20010,#20009) +#20012=@"var;{arguments};{#20008}" +variables(#20012,"arguments",#20008) +isArgumentsObject(#20012) +#20013=* +stmts(#20013,1,#20004,-2,"{}") +#20014=@"loc,{#10000},1,18,1,19" +locations_default(#20014,#10000,1,18,1,19) +hasLocation(#20013,#20014) +stmtContainers(#20013,#20004) +numlines(#20004,1,1,0) +#20015=* +stmts(#20015,17,#20001,1,"functio ... T[]) {}") +#20016=@"loc,{#10000},2,1,2,21" +locations_default(#20016,#10000,2,1,2,21) +hasLocation(#20015,#20016) +stmtContainers(#20015,#20001) +#20017=* +exprs(#20017,78,#20015,-1,"f") +#20018=@"loc,{#10000},2,10,2,10" +locations_default(#20018,#10000,2,10,2,10) +hasLocation(#20017,#20018) +exprContainers(#20017,#20015) +literals("f","f",#20017) +decl(#20017,#20003) +#20019=* +scopes(#20019,1) +scopenodes(#20015,#20019) +scopenesting(#20019,#20000) +#20020=@"var;{p};{#20019}" +variables(#20020,"p",#20019) +#20021=* +exprs(#20021,78,#20015,0,"p: T[]") +#20022=@"loc,{#10000},2,12,2,17" +locations_default(#20022,#10000,2,12,2,17) +hasLocation(#20021,#20022) +exprContainers(#20021,#20015) +literals("p","p",#20021) +decl(#20021,#20020) +#20023=@"var;{arguments};{#20019}" +variables(#20023,"arguments",#20019) +isArgumentsObject(#20023) +#20024=* +stmts(#20024,1,#20015,-2,"{}") +#20025=@"loc,{#10000},2,20,2,21" +locations_default(#20025,#10000,2,20,2,21) +hasLocation(#20024,#20025) +stmtContainers(#20024,#20015) +numlines(#20015,1,1,0) +#20026=* +stmts(#20026,17,#20001,2,"functio ... ][]) {}") +#20027=@"loc,{#10000},3,1,3,23" +locations_default(#20027,#10000,3,1,3,23) +hasLocation(#20026,#20027) +stmtContainers(#20026,#20001) +#20028=* +exprs(#20028,78,#20026,-1,"f") +#20029=@"loc,{#10000},3,10,3,10" +locations_default(#20029,#10000,3,10,3,10) +hasLocation(#20028,#20029) +exprContainers(#20028,#20026) +literals("f","f",#20028) +decl(#20028,#20003) +#20030=* +scopes(#20030,1) +scopenodes(#20026,#20030) +scopenesting(#20030,#20000) +#20031=@"var;{p};{#20030}" +variables(#20031,"p",#20030) +#20032=* +exprs(#20032,78,#20026,0,"p: T[][]") +#20033=@"loc,{#10000},3,12,3,19" +locations_default(#20033,#10000,3,12,3,19) +hasLocation(#20032,#20033) +exprContainers(#20032,#20026) +literals("p","p",#20032) +decl(#20032,#20031) +#20034=@"var;{arguments};{#20030}" +variables(#20034,"arguments",#20030) +isArgumentsObject(#20034) +#20035=* +stmts(#20035,1,#20026,-2,"{}") +#20036=@"loc,{#10000},3,22,3,23" +locations_default(#20036,#10000,3,22,3,23) +hasLocation(#20035,#20036) +stmtContainers(#20035,#20026) +numlines(#20026,1,1,0) +#20037=* +stmts(#20037,17,#20001,3,"functio ... ][]) {}") +#20038=@"loc,{#10000},4,1,4,25" +locations_default(#20038,#10000,4,1,4,25) +hasLocation(#20037,#20038) +stmtContainers(#20037,#20001) +#20039=* +exprs(#20039,78,#20037,-1,"f") +#20040=@"loc,{#10000},4,10,4,10" +locations_default(#20040,#10000,4,10,4,10) +hasLocation(#20039,#20040) +exprContainers(#20039,#20037) +literals("f","f",#20039) +decl(#20039,#20003) +#20041=* +scopes(#20041,1) +scopenodes(#20037,#20041) +scopenesting(#20041,#20000) +#20042=@"var;{p};{#20041}" +variables(#20042,"p",#20041) +#20043=* +exprs(#20043,78,#20037,0,"p: T[][][]") +#20044=@"loc,{#10000},4,12,4,21" +locations_default(#20044,#10000,4,12,4,21) +hasLocation(#20043,#20044) +exprContainers(#20043,#20037) +literals("p","p",#20043) +decl(#20043,#20042) +#20045=@"var;{arguments};{#20041}" +variables(#20045,"arguments",#20041) +isArgumentsObject(#20045) +#20046=* +stmts(#20046,1,#20037,-2,"{}") +#20047=@"loc,{#10000},4,24,4,25" +locations_default(#20047,#10000,4,24,4,25) +hasLocation(#20046,#20047) +stmtContainers(#20046,#20037) +numlines(#20037,1,1,0) +#20048=* +lines(#20048,#20001,"function f(p: T) {}"," +") +hasLocation(#20048,#20005) +#20049=* +lines(#20049,#20001,"function f(p: T[]) {}"," +") +hasLocation(#20049,#20016) +#20050=* +lines(#20050,#20001,"function f(p: T[][]) {}"," +") +hasLocation(#20050,#20027) +#20051=* +lines(#20051,#20001,"function f(p: T[][][]) {}"," +") +hasLocation(#20051,#20038) +numlines(#20001,4,4,0) +#20052=* +tokeninfo(#20052,7,#20001,0,"function") +#20053=@"loc,{#10000},1,1,1,8" +locations_default(#20053,#10000,1,1,1,8) +hasLocation(#20052,#20053) +#20054=* +tokeninfo(#20054,6,#20001,1,"f") +hasLocation(#20054,#20007) +#20055=* +tokeninfo(#20055,8,#20001,2,"(") +#20056=@"loc,{#10000},1,11,1,11" +locations_default(#20056,#10000,1,11,1,11) +hasLocation(#20055,#20056) +#20057=* +tokeninfo(#20057,6,#20001,3,"p") +#20058=@"loc,{#10000},1,12,1,12" +locations_default(#20058,#10000,1,12,1,12) +hasLocation(#20057,#20058) +#20059=* +tokeninfo(#20059,8,#20001,4,":") +#20060=@"loc,{#10000},1,13,1,13" +locations_default(#20060,#10000,1,13,1,13) +hasLocation(#20059,#20060) +#20061=* +tokeninfo(#20061,6,#20001,5,"T") +#20062=@"loc,{#10000},1,15,1,15" +locations_default(#20062,#10000,1,15,1,15) +hasLocation(#20061,#20062) +#20063=* +tokeninfo(#20063,8,#20001,6,")") +#20064=@"loc,{#10000},1,16,1,16" +locations_default(#20064,#10000,1,16,1,16) +hasLocation(#20063,#20064) +#20065=* +tokeninfo(#20065,8,#20001,7,"{") +#20066=@"loc,{#10000},1,18,1,18" +locations_default(#20066,#10000,1,18,1,18) +hasLocation(#20065,#20066) +#20067=* +tokeninfo(#20067,8,#20001,8,"}") +#20068=@"loc,{#10000},1,19,1,19" +locations_default(#20068,#10000,1,19,1,19) +hasLocation(#20067,#20068) +#20069=* +tokeninfo(#20069,7,#20001,9,"function") +#20070=@"loc,{#10000},2,1,2,8" +locations_default(#20070,#10000,2,1,2,8) +hasLocation(#20069,#20070) +#20071=* +tokeninfo(#20071,6,#20001,10,"f") +hasLocation(#20071,#20018) +#20072=* +tokeninfo(#20072,8,#20001,11,"(") +#20073=@"loc,{#10000},2,11,2,11" +locations_default(#20073,#10000,2,11,2,11) +hasLocation(#20072,#20073) +#20074=* +tokeninfo(#20074,6,#20001,12,"p") +#20075=@"loc,{#10000},2,12,2,12" +locations_default(#20075,#10000,2,12,2,12) +hasLocation(#20074,#20075) +#20076=* +tokeninfo(#20076,8,#20001,13,":") +#20077=@"loc,{#10000},2,13,2,13" +locations_default(#20077,#10000,2,13,2,13) +hasLocation(#20076,#20077) +#20078=* +tokeninfo(#20078,6,#20001,14,"T") +#20079=@"loc,{#10000},2,15,2,15" +locations_default(#20079,#10000,2,15,2,15) +hasLocation(#20078,#20079) +#20080=* +tokeninfo(#20080,8,#20001,15,"[") +#20081=@"loc,{#10000},2,16,2,16" +locations_default(#20081,#10000,2,16,2,16) +hasLocation(#20080,#20081) +#20082=* +tokeninfo(#20082,8,#20001,16,"]") +#20083=@"loc,{#10000},2,17,2,17" +locations_default(#20083,#10000,2,17,2,17) +hasLocation(#20082,#20083) +#20084=* +tokeninfo(#20084,8,#20001,17,")") +#20085=@"loc,{#10000},2,18,2,18" +locations_default(#20085,#10000,2,18,2,18) +hasLocation(#20084,#20085) +#20086=* +tokeninfo(#20086,8,#20001,18,"{") +#20087=@"loc,{#10000},2,20,2,20" +locations_default(#20087,#10000,2,20,2,20) +hasLocation(#20086,#20087) +#20088=* +tokeninfo(#20088,8,#20001,19,"}") +#20089=@"loc,{#10000},2,21,2,21" +locations_default(#20089,#10000,2,21,2,21) +hasLocation(#20088,#20089) +#20090=* +tokeninfo(#20090,7,#20001,20,"function") +#20091=@"loc,{#10000},3,1,3,8" +locations_default(#20091,#10000,3,1,3,8) +hasLocation(#20090,#20091) +#20092=* +tokeninfo(#20092,6,#20001,21,"f") +hasLocation(#20092,#20029) +#20093=* +tokeninfo(#20093,8,#20001,22,"(") +#20094=@"loc,{#10000},3,11,3,11" +locations_default(#20094,#10000,3,11,3,11) +hasLocation(#20093,#20094) +#20095=* +tokeninfo(#20095,6,#20001,23,"p") +#20096=@"loc,{#10000},3,12,3,12" +locations_default(#20096,#10000,3,12,3,12) +hasLocation(#20095,#20096) +#20097=* +tokeninfo(#20097,8,#20001,24,":") +#20098=@"loc,{#10000},3,13,3,13" +locations_default(#20098,#10000,3,13,3,13) +hasLocation(#20097,#20098) +#20099=* +tokeninfo(#20099,6,#20001,25,"T") +#20100=@"loc,{#10000},3,15,3,15" +locations_default(#20100,#10000,3,15,3,15) +hasLocation(#20099,#20100) +#20101=* +tokeninfo(#20101,8,#20001,26,"[") +#20102=@"loc,{#10000},3,16,3,16" +locations_default(#20102,#10000,3,16,3,16) +hasLocation(#20101,#20102) +#20103=* +tokeninfo(#20103,8,#20001,27,"]") +#20104=@"loc,{#10000},3,17,3,17" +locations_default(#20104,#10000,3,17,3,17) +hasLocation(#20103,#20104) +#20105=* +tokeninfo(#20105,8,#20001,28,"[") +#20106=@"loc,{#10000},3,18,3,18" +locations_default(#20106,#10000,3,18,3,18) +hasLocation(#20105,#20106) +#20107=* +tokeninfo(#20107,8,#20001,29,"]") +#20108=@"loc,{#10000},3,19,3,19" +locations_default(#20108,#10000,3,19,3,19) +hasLocation(#20107,#20108) +#20109=* +tokeninfo(#20109,8,#20001,30,")") +#20110=@"loc,{#10000},3,20,3,20" +locations_default(#20110,#10000,3,20,3,20) +hasLocation(#20109,#20110) +#20111=* +tokeninfo(#20111,8,#20001,31,"{") +#20112=@"loc,{#10000},3,22,3,22" +locations_default(#20112,#10000,3,22,3,22) +hasLocation(#20111,#20112) +#20113=* +tokeninfo(#20113,8,#20001,32,"}") +#20114=@"loc,{#10000},3,23,3,23" +locations_default(#20114,#10000,3,23,3,23) +hasLocation(#20113,#20114) +#20115=* +tokeninfo(#20115,7,#20001,33,"function") +#20116=@"loc,{#10000},4,1,4,8" +locations_default(#20116,#10000,4,1,4,8) +hasLocation(#20115,#20116) +#20117=* +tokeninfo(#20117,6,#20001,34,"f") +hasLocation(#20117,#20040) +#20118=* +tokeninfo(#20118,8,#20001,35,"(") +#20119=@"loc,{#10000},4,11,4,11" +locations_default(#20119,#10000,4,11,4,11) +hasLocation(#20118,#20119) +#20120=* +tokeninfo(#20120,6,#20001,36,"p") +#20121=@"loc,{#10000},4,12,4,12" +locations_default(#20121,#10000,4,12,4,12) +hasLocation(#20120,#20121) +#20122=* +tokeninfo(#20122,8,#20001,37,":") +#20123=@"loc,{#10000},4,13,4,13" +locations_default(#20123,#10000,4,13,4,13) +hasLocation(#20122,#20123) +#20124=* +tokeninfo(#20124,6,#20001,38,"T") +#20125=@"loc,{#10000},4,15,4,15" +locations_default(#20125,#10000,4,15,4,15) +hasLocation(#20124,#20125) +#20126=* +tokeninfo(#20126,8,#20001,39,"[") +#20127=@"loc,{#10000},4,16,4,16" +locations_default(#20127,#10000,4,16,4,16) +hasLocation(#20126,#20127) +#20128=* +tokeninfo(#20128,8,#20001,40,"]") +#20129=@"loc,{#10000},4,17,4,17" +locations_default(#20129,#10000,4,17,4,17) +hasLocation(#20128,#20129) +#20130=* +tokeninfo(#20130,8,#20001,41,"[") +#20131=@"loc,{#10000},4,18,4,18" +locations_default(#20131,#10000,4,18,4,18) +hasLocation(#20130,#20131) +#20132=* +tokeninfo(#20132,8,#20001,42,"]") +#20133=@"loc,{#10000},4,19,4,19" +locations_default(#20133,#10000,4,19,4,19) +hasLocation(#20132,#20133) +#20134=* +tokeninfo(#20134,8,#20001,43,"[") +#20135=@"loc,{#10000},4,20,4,20" +locations_default(#20135,#10000,4,20,4,20) +hasLocation(#20134,#20135) +#20136=* +tokeninfo(#20136,8,#20001,44,"]") +#20137=@"loc,{#10000},4,21,4,21" +locations_default(#20137,#10000,4,21,4,21) +hasLocation(#20136,#20137) +#20138=* +tokeninfo(#20138,8,#20001,45,")") +#20139=@"loc,{#10000},4,22,4,22" +locations_default(#20139,#10000,4,22,4,22) +hasLocation(#20138,#20139) +#20140=* +tokeninfo(#20140,8,#20001,46,"{") +#20141=@"loc,{#10000},4,24,4,24" +locations_default(#20141,#10000,4,24,4,24) +hasLocation(#20140,#20141) +#20142=* +tokeninfo(#20142,8,#20001,47,"}") +#20143=@"loc,{#10000},4,25,4,25" +locations_default(#20143,#10000,4,25,4,25) +hasLocation(#20142,#20143) +#20144=* +tokeninfo(#20144,0,#20001,48,"") +#20145=@"loc,{#10000},5,1,5,0" +locations_default(#20145,#10000,5,1,5,0) +hasLocation(#20144,#20145) +#20146=* +entry_cfg_node(#20146,#20001) +#20147=@"loc,{#10000},1,1,1,0" +locations_default(#20147,#10000,1,1,1,0) +hasLocation(#20146,#20147) +#20148=* +exit_cfg_node(#20148,#20001) +hasLocation(#20148,#20145) +successor(#20037,#20148) +#20149=* +entry_cfg_node(#20149,#20037) +#20150=@"loc,{#10000},4,1,4,0" +locations_default(#20150,#10000,4,1,4,0) +hasLocation(#20149,#20150) +#20151=* +exit_cfg_node(#20151,#20037) +#20152=@"loc,{#10000},4,26,4,25" +locations_default(#20152,#10000,4,26,4,25) +hasLocation(#20151,#20152) +successor(#20046,#20151) +successor(#20043,#20046) +successor(#20149,#20043) +successor(#20026,#20037) +#20153=* +entry_cfg_node(#20153,#20026) +#20154=@"loc,{#10000},3,1,3,0" +locations_default(#20154,#10000,3,1,3,0) +hasLocation(#20153,#20154) +#20155=* +exit_cfg_node(#20155,#20026) +#20156=@"loc,{#10000},3,24,3,23" +locations_default(#20156,#10000,3,24,3,23) +hasLocation(#20155,#20156) +successor(#20035,#20155) +successor(#20032,#20035) +successor(#20153,#20032) +successor(#20015,#20026) +#20157=* +entry_cfg_node(#20157,#20015) +#20158=@"loc,{#10000},2,1,2,0" +locations_default(#20158,#10000,2,1,2,0) +hasLocation(#20157,#20158) +#20159=* +exit_cfg_node(#20159,#20015) +#20160=@"loc,{#10000},2,22,2,21" +locations_default(#20160,#10000,2,22,2,21) +hasLocation(#20159,#20160) +successor(#20024,#20159) +successor(#20021,#20024) +successor(#20157,#20021) +successor(#20004,#20015) +#20161=* +entry_cfg_node(#20161,#20004) +hasLocation(#20161,#20147) +#20162=* +exit_cfg_node(#20162,#20004) +#20163=@"loc,{#10000},1,20,1,19" +locations_default(#20163,#10000,1,20,1,19) +hasLocation(#20162,#20163) +successor(#20013,#20162) +successor(#20010,#20013) +successor(#20161,#20010) +successor(#20039,#20004) +successor(#20028,#20039) +successor(#20017,#20028) +successor(#20006,#20017) +successor(#20146,#20006) +numlines(#10000,4,4,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/flow/output/trap/declared-module-imports.js.trap b/javascript/extractor/tests/flow/output/trap/declared-module-imports.js.trap new file mode 100644 index 000000000000..d341a31f7571 --- /dev/null +++ b/javascript/extractor/tests/flow/output/trap/declared-module-imports.js.trap @@ -0,0 +1,146 @@ +#10000=@"/declared-module-imports.js;sourcefile" +files(#10000,"/declared-module-imports.js","declared-module-imports","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,5,0" +locations_default(#20002,#10000,1,1,5,0) +hasLocation(#20001,#20002) +#20003=@"module;{#10000},1,1" +scopes(#20003,3) +scopenodes(#20001,#20003) +scopenesting(#20003,#20000) +isModule(#20001) +#20004=* +lines(#20004,#20001,"declare module ""m1"" {"," +") +#20005=@"loc,{#10000},1,1,1,21" +locations_default(#20005,#10000,1,1,1,21) +hasLocation(#20004,#20005) +#20006=* +lines(#20006,#20001," import type t from ""m2"";"," +") +#20007=@"loc,{#10000},2,1,2,26" +locations_default(#20007,#10000,2,1,2,26) +hasLocation(#20006,#20007) +indentation(#10000,2," ",2) +#20008=* +lines(#20008,#20001," import typeof t from ""m3"";"," +") +#20009=@"loc,{#10000},3,1,3,28" +locations_default(#20009,#10000,3,1,3,28) +hasLocation(#20008,#20009) +indentation(#10000,3," ",2) +#20010=* +lines(#20010,#20001,"}"," +") +#20011=@"loc,{#10000},4,1,4,1" +locations_default(#20011,#10000,4,1,4,1) +hasLocation(#20010,#20011) +numlines(#20001,4,4,0) +#20012=* +tokeninfo(#20012,6,#20001,0,"declare") +#20013=@"loc,{#10000},1,1,1,7" +locations_default(#20013,#10000,1,1,1,7) +hasLocation(#20012,#20013) +#20014=* +tokeninfo(#20014,6,#20001,1,"module") +#20015=@"loc,{#10000},1,9,1,14" +locations_default(#20015,#10000,1,9,1,14) +hasLocation(#20014,#20015) +#20016=* +tokeninfo(#20016,4,#20001,2,"""m1""") +#20017=@"loc,{#10000},1,16,1,19" +locations_default(#20017,#10000,1,16,1,19) +hasLocation(#20016,#20017) +#20018=* +tokeninfo(#20018,8,#20001,3,"{") +#20019=@"loc,{#10000},1,21,1,21" +locations_default(#20019,#10000,1,21,1,21) +hasLocation(#20018,#20019) +#20020=* +tokeninfo(#20020,7,#20001,4,"import") +#20021=@"loc,{#10000},2,3,2,8" +locations_default(#20021,#10000,2,3,2,8) +hasLocation(#20020,#20021) +#20022=* +tokeninfo(#20022,6,#20001,5,"type") +#20023=@"loc,{#10000},2,10,2,13" +locations_default(#20023,#10000,2,10,2,13) +hasLocation(#20022,#20023) +#20024=* +tokeninfo(#20024,6,#20001,6,"t") +#20025=@"loc,{#10000},2,15,2,15" +locations_default(#20025,#10000,2,15,2,15) +hasLocation(#20024,#20025) +#20026=* +tokeninfo(#20026,6,#20001,7,"from") +#20027=@"loc,{#10000},2,17,2,20" +locations_default(#20027,#10000,2,17,2,20) +hasLocation(#20026,#20027) +#20028=* +tokeninfo(#20028,4,#20001,8,"""m2""") +#20029=@"loc,{#10000},2,22,2,25" +locations_default(#20029,#10000,2,22,2,25) +hasLocation(#20028,#20029) +#20030=* +tokeninfo(#20030,8,#20001,9,";") +#20031=@"loc,{#10000},2,26,2,26" +locations_default(#20031,#10000,2,26,2,26) +hasLocation(#20030,#20031) +#20032=* +tokeninfo(#20032,7,#20001,10,"import") +#20033=@"loc,{#10000},3,3,3,8" +locations_default(#20033,#10000,3,3,3,8) +hasLocation(#20032,#20033) +#20034=* +tokeninfo(#20034,7,#20001,11,"typeof") +#20035=@"loc,{#10000},3,10,3,15" +locations_default(#20035,#10000,3,10,3,15) +hasLocation(#20034,#20035) +#20036=* +tokeninfo(#20036,6,#20001,12,"t") +#20037=@"loc,{#10000},3,17,3,17" +locations_default(#20037,#10000,3,17,3,17) +hasLocation(#20036,#20037) +#20038=* +tokeninfo(#20038,6,#20001,13,"from") +#20039=@"loc,{#10000},3,19,3,22" +locations_default(#20039,#10000,3,19,3,22) +hasLocation(#20038,#20039) +#20040=* +tokeninfo(#20040,4,#20001,14,"""m3""") +#20041=@"loc,{#10000},3,24,3,27" +locations_default(#20041,#10000,3,24,3,27) +hasLocation(#20040,#20041) +#20042=* +tokeninfo(#20042,8,#20001,15,";") +#20043=@"loc,{#10000},3,28,3,28" +locations_default(#20043,#10000,3,28,3,28) +hasLocation(#20042,#20043) +#20044=* +tokeninfo(#20044,8,#20001,16,"}") +hasLocation(#20044,#20011) +#20045=* +tokeninfo(#20045,0,#20001,17,"") +#20046=@"loc,{#10000},5,1,5,0" +locations_default(#20046,#10000,5,1,5,0) +hasLocation(#20045,#20046) +#20047=* +entry_cfg_node(#20047,#20001) +#20048=@"loc,{#10000},1,1,1,0" +locations_default(#20048,#10000,1,1,1,0) +hasLocation(#20047,#20048) +#20049=* +exit_cfg_node(#20049,#20001) +hasLocation(#20049,#20046) +successor(#20047,#20049) +numlines(#10000,4,4,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/flow/output/trap/get-set-methods.js.trap b/javascript/extractor/tests/flow/output/trap/get-set-methods.js.trap new file mode 100644 index 000000000000..b179d7ec365e --- /dev/null +++ b/javascript/extractor/tests/flow/output/trap/get-set-methods.js.trap @@ -0,0 +1,368 @@ +#10000=@"/get-set-methods.js;sourcefile" +files(#10000,"/get-set-methods.js","get-set-methods","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,5,0" +locations_default(#20002,#10000,1,1,5,0) +hasLocation(#20001,#20002) +#20003=@"var;{C};{#20000}" +variables(#20003,"C",#20000) +#20004=@"local_type_name;{C};{#20000}" +local_type_names(#20004,"C",#20000) +#20005=* +stmts(#20005,26,#20001,0,"class C ... ) { }\n}") +#20006=@"loc,{#10000},1,1,4,1" +locations_default(#20006,#10000,1,1,4,1) +hasLocation(#20005,#20006) +stmtContainers(#20005,#20001) +#20007=* +exprs(#20007,78,#20005,0,"C") +#20008=@"loc,{#10000},1,7,1,7" +locations_default(#20008,#10000,1,7,1,7) +hasLocation(#20007,#20008) +enclosingStmt(#20007,#20005) +exprContainers(#20007,#20001) +literals("C","C",#20007) +decl(#20007,#20003) +typedecl(#20007,#20004) +#20009=* +scopes(#20009,10) +scopenodes(#20005,#20009) +scopenesting(#20009,#20000) +#20010=* +properties(#20010,#20005,2,0,"get(v: T) { }") +#20011=@"loc,{#10000},2,5,2,20" +locations_default(#20011,#10000,2,5,2,20) +hasLocation(#20010,#20011) +#20012=* +exprs(#20012,0,#20010,0,"get") +#20013=@"loc,{#10000},2,5,2,7" +locations_default(#20013,#10000,2,5,2,7) +hasLocation(#20012,#20013) +enclosingStmt(#20012,#20005) +exprContainers(#20012,#20001) +literals("get","get",#20012) +#20014=* +exprs(#20014,9,#20010,1,"(v: T) { }") +#20015=@"loc,{#10000},2,11,2,20" +locations_default(#20015,#10000,2,11,2,20) +hasLocation(#20014,#20015) +enclosingStmt(#20014,#20005) +exprContainers(#20014,#20001) +#20016=* +scopes(#20016,1) +scopenodes(#20014,#20016) +scopenesting(#20016,#20009) +#20017=@"var;{v};{#20016}" +variables(#20017,"v",#20016) +#20018=* +exprs(#20018,78,#20014,0,"v: T") +#20019=@"loc,{#10000},2,12,2,15" +locations_default(#20019,#10000,2,12,2,15) +hasLocation(#20018,#20019) +exprContainers(#20018,#20014) +literals("v","v",#20018) +decl(#20018,#20017) +#20020=@"var;{arguments};{#20016}" +variables(#20020,"arguments",#20016) +isArgumentsObject(#20020) +#20021=* +stmts(#20021,1,#20014,-2,"{ }") +#20022=@"loc,{#10000},2,18,2,20" +locations_default(#20022,#10000,2,18,2,20) +hasLocation(#20021,#20022) +stmtContainers(#20021,#20014) +numlines(#20014,1,1,0) +isMethod(#20010) +#20023=* +properties(#20023,#20005,3,0,"set(v: T) { }") +#20024=@"loc,{#10000},3,5,3,20" +locations_default(#20024,#10000,3,5,3,20) +hasLocation(#20023,#20024) +#20025=* +exprs(#20025,0,#20023,0,"set") +#20026=@"loc,{#10000},3,5,3,7" +locations_default(#20026,#10000,3,5,3,7) +hasLocation(#20025,#20026) +enclosingStmt(#20025,#20005) +exprContainers(#20025,#20001) +literals("set","set",#20025) +#20027=* +exprs(#20027,9,#20023,1,"(v: T) { }") +#20028=@"loc,{#10000},3,11,3,20" +locations_default(#20028,#10000,3,11,3,20) +hasLocation(#20027,#20028) +enclosingStmt(#20027,#20005) +exprContainers(#20027,#20001) +#20029=* +scopes(#20029,1) +scopenodes(#20027,#20029) +scopenesting(#20029,#20009) +#20030=@"var;{v};{#20029}" +variables(#20030,"v",#20029) +#20031=* +exprs(#20031,78,#20027,0,"v: T") +#20032=@"loc,{#10000},3,12,3,15" +locations_default(#20032,#10000,3,12,3,15) +hasLocation(#20031,#20032) +exprContainers(#20031,#20027) +literals("v","v",#20031) +decl(#20031,#20030) +#20033=@"var;{arguments};{#20029}" +variables(#20033,"arguments",#20029) +isArgumentsObject(#20033) +#20034=* +stmts(#20034,1,#20027,-2,"{ }") +#20035=@"loc,{#10000},3,18,3,20" +locations_default(#20035,#10000,3,18,3,20) +hasLocation(#20034,#20035) +stmtContainers(#20034,#20027) +numlines(#20027,1,1,0) +isMethod(#20023) +#20036=* +properties(#20036,#20005,4,0,"constructor() {}") +#20037=@"loc,{#10000},1,9,1,8" +locations_default(#20037,#10000,1,9,1,8) +hasLocation(#20036,#20037) +#20038=* +exprs(#20038,0,#20036,0,"constructor") +hasLocation(#20038,#20037) +enclosingStmt(#20038,#20005) +exprContainers(#20038,#20001) +literals("constructor","constructor",#20038) +#20039=* +exprs(#20039,9,#20036,1,"() {}") +hasLocation(#20039,#20037) +enclosingStmt(#20039,#20005) +exprContainers(#20039,#20001) +#20040=* +scopes(#20040,1) +scopenodes(#20039,#20040) +scopenesting(#20040,#20009) +#20041=@"var;{arguments};{#20040}" +variables(#20041,"arguments",#20040) +isArgumentsObject(#20041) +#20042=* +stmts(#20042,1,#20039,-2,"{}") +hasLocation(#20042,#20037) +stmtContainers(#20042,#20039) +numlines(#20039,1,0,0) +isMethod(#20036) +#20043=* +lines(#20043,#20001,"class C {"," +") +#20044=@"loc,{#10000},1,1,1,9" +locations_default(#20044,#10000,1,1,1,9) +hasLocation(#20043,#20044) +#20045=* +lines(#20045,#20001," get(v: T) { }"," +") +#20046=@"loc,{#10000},2,1,2,20" +locations_default(#20046,#10000,2,1,2,20) +hasLocation(#20045,#20046) +indentation(#10000,2," ",4) +#20047=* +lines(#20047,#20001," set(v: T) { }"," +") +#20048=@"loc,{#10000},3,1,3,20" +locations_default(#20048,#10000,3,1,3,20) +hasLocation(#20047,#20048) +indentation(#10000,3," ",4) +#20049=* +lines(#20049,#20001,"}"," +") +#20050=@"loc,{#10000},4,1,4,1" +locations_default(#20050,#10000,4,1,4,1) +hasLocation(#20049,#20050) +numlines(#20001,4,4,0) +#20051=* +tokeninfo(#20051,7,#20001,0,"class") +#20052=@"loc,{#10000},1,1,1,5" +locations_default(#20052,#10000,1,1,1,5) +hasLocation(#20051,#20052) +#20053=* +tokeninfo(#20053,6,#20001,1,"C") +hasLocation(#20053,#20008) +#20054=* +tokeninfo(#20054,8,#20001,2,"{") +#20055=@"loc,{#10000},1,9,1,9" +locations_default(#20055,#10000,1,9,1,9) +hasLocation(#20054,#20055) +#20056=* +tokeninfo(#20056,6,#20001,3,"get") +hasLocation(#20056,#20013) +#20057=* +tokeninfo(#20057,8,#20001,4,"<") +#20058=@"loc,{#10000},2,8,2,8" +locations_default(#20058,#10000,2,8,2,8) +hasLocation(#20057,#20058) +#20059=* +tokeninfo(#20059,6,#20001,5,"T") +#20060=@"loc,{#10000},2,9,2,9" +locations_default(#20060,#10000,2,9,2,9) +hasLocation(#20059,#20060) +#20061=* +tokeninfo(#20061,8,#20001,6,">") +#20062=@"loc,{#10000},2,10,2,10" +locations_default(#20062,#10000,2,10,2,10) +hasLocation(#20061,#20062) +#20063=* +tokeninfo(#20063,8,#20001,7,"(") +#20064=@"loc,{#10000},2,11,2,11" +locations_default(#20064,#10000,2,11,2,11) +hasLocation(#20063,#20064) +#20065=* +tokeninfo(#20065,6,#20001,8,"v") +#20066=@"loc,{#10000},2,12,2,12" +locations_default(#20066,#10000,2,12,2,12) +hasLocation(#20065,#20066) +#20067=* +tokeninfo(#20067,8,#20001,9,":") +#20068=@"loc,{#10000},2,13,2,13" +locations_default(#20068,#10000,2,13,2,13) +hasLocation(#20067,#20068) +#20069=* +tokeninfo(#20069,6,#20001,10,"T") +#20070=@"loc,{#10000},2,15,2,15" +locations_default(#20070,#10000,2,15,2,15) +hasLocation(#20069,#20070) +#20071=* +tokeninfo(#20071,8,#20001,11,")") +#20072=@"loc,{#10000},2,16,2,16" +locations_default(#20072,#10000,2,16,2,16) +hasLocation(#20071,#20072) +#20073=* +tokeninfo(#20073,8,#20001,12,"{") +#20074=@"loc,{#10000},2,18,2,18" +locations_default(#20074,#10000,2,18,2,18) +hasLocation(#20073,#20074) +#20075=* +tokeninfo(#20075,8,#20001,13,"}") +#20076=@"loc,{#10000},2,20,2,20" +locations_default(#20076,#10000,2,20,2,20) +hasLocation(#20075,#20076) +#20077=* +tokeninfo(#20077,6,#20001,14,"set") +hasLocation(#20077,#20026) +#20078=* +tokeninfo(#20078,8,#20001,15,"<") +#20079=@"loc,{#10000},3,8,3,8" +locations_default(#20079,#10000,3,8,3,8) +hasLocation(#20078,#20079) +#20080=* +tokeninfo(#20080,6,#20001,16,"T") +#20081=@"loc,{#10000},3,9,3,9" +locations_default(#20081,#10000,3,9,3,9) +hasLocation(#20080,#20081) +#20082=* +tokeninfo(#20082,8,#20001,17,">") +#20083=@"loc,{#10000},3,10,3,10" +locations_default(#20083,#10000,3,10,3,10) +hasLocation(#20082,#20083) +#20084=* +tokeninfo(#20084,8,#20001,18,"(") +#20085=@"loc,{#10000},3,11,3,11" +locations_default(#20085,#10000,3,11,3,11) +hasLocation(#20084,#20085) +#20086=* +tokeninfo(#20086,6,#20001,19,"v") +#20087=@"loc,{#10000},3,12,3,12" +locations_default(#20087,#10000,3,12,3,12) +hasLocation(#20086,#20087) +#20088=* +tokeninfo(#20088,8,#20001,20,":") +#20089=@"loc,{#10000},3,13,3,13" +locations_default(#20089,#10000,3,13,3,13) +hasLocation(#20088,#20089) +#20090=* +tokeninfo(#20090,6,#20001,21,"T") +#20091=@"loc,{#10000},3,15,3,15" +locations_default(#20091,#10000,3,15,3,15) +hasLocation(#20090,#20091) +#20092=* +tokeninfo(#20092,8,#20001,22,")") +#20093=@"loc,{#10000},3,16,3,16" +locations_default(#20093,#10000,3,16,3,16) +hasLocation(#20092,#20093) +#20094=* +tokeninfo(#20094,8,#20001,23,"{") +#20095=@"loc,{#10000},3,18,3,18" +locations_default(#20095,#10000,3,18,3,18) +hasLocation(#20094,#20095) +#20096=* +tokeninfo(#20096,8,#20001,24,"}") +#20097=@"loc,{#10000},3,20,3,20" +locations_default(#20097,#10000,3,20,3,20) +hasLocation(#20096,#20097) +#20098=* +tokeninfo(#20098,8,#20001,25,"}") +hasLocation(#20098,#20050) +#20099=* +tokeninfo(#20099,0,#20001,26,"") +#20100=@"loc,{#10000},5,1,5,0" +locations_default(#20100,#10000,5,1,5,0) +hasLocation(#20099,#20100) +#20101=* +entry_cfg_node(#20101,#20001) +#20102=@"loc,{#10000},1,1,1,0" +locations_default(#20102,#10000,1,1,1,0) +hasLocation(#20101,#20102) +#20103=* +exit_cfg_node(#20103,#20001) +hasLocation(#20103,#20100) +successor(#20039,#20036) +#20104=* +entry_cfg_node(#20104,#20039) +hasLocation(#20104,#20037) +#20105=* +exit_cfg_node(#20105,#20039) +hasLocation(#20105,#20037) +successor(#20042,#20105) +successor(#20104,#20042) +successor(#20038,#20039) +successor(#20036,#20005) +successor(#20027,#20023) +#20106=* +entry_cfg_node(#20106,#20027) +#20107=@"loc,{#10000},3,11,3,10" +locations_default(#20107,#10000,3,11,3,10) +hasLocation(#20106,#20107) +#20108=* +exit_cfg_node(#20108,#20027) +#20109=@"loc,{#10000},3,21,3,20" +locations_default(#20109,#10000,3,21,3,20) +hasLocation(#20108,#20109) +successor(#20034,#20108) +successor(#20031,#20034) +successor(#20106,#20031) +successor(#20025,#20027) +successor(#20023,#20038) +successor(#20014,#20010) +#20110=* +entry_cfg_node(#20110,#20014) +#20111=@"loc,{#10000},2,11,2,10" +locations_default(#20111,#10000,2,11,2,10) +hasLocation(#20110,#20111) +#20112=* +exit_cfg_node(#20112,#20014) +#20113=@"loc,{#10000},2,21,2,20" +locations_default(#20113,#10000,2,21,2,20) +hasLocation(#20112,#20113) +successor(#20021,#20112) +successor(#20018,#20021) +successor(#20110,#20018) +successor(#20012,#20014) +successor(#20010,#20025) +successor(#20007,#20012) +successor(#20005,#20103) +successor(#20101,#20007) +numlines(#10000,4,4,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/flow/output/trap/predicate-function-annotation.js.trap b/javascript/extractor/tests/flow/output/trap/predicate-function-annotation.js.trap new file mode 100644 index 000000000000..11b134ed0918 --- /dev/null +++ b/javascript/extractor/tests/flow/output/trap/predicate-function-annotation.js.trap @@ -0,0 +1,539 @@ +#10000=@"/predicate-function-annotation.js;sourcefile" +files(#10000,"/predicate-function-annotation.js","predicate-function-annotation","js",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,10,0" +locations_default(#20002,#10000,1,1,10,0) +hasLocation(#20001,#20002) +#20003=@"var;{f};{#20000}" +variables(#20003,"f",#20000) +#20004=@"var;{g};{#20000}" +variables(#20004,"g",#20000) +#20005=* +stmts(#20005,17,#20001,0,"functio ... rn a;\n}") +#20006=@"loc,{#10000},1,1,3,1" +locations_default(#20006,#10000,1,1,3,1) +hasLocation(#20005,#20006) +stmtContainers(#20005,#20001) +#20007=* +exprs(#20007,78,#20005,-1,"f") +#20008=@"loc,{#10000},1,10,1,10" +locations_default(#20008,#10000,1,10,1,10) +hasLocation(#20007,#20008) +exprContainers(#20007,#20005) +literals("f","f",#20007) +decl(#20007,#20003) +#20009=* +scopes(#20009,1) +scopenodes(#20005,#20009) +scopenesting(#20009,#20000) +#20010=@"var;{a};{#20009}" +variables(#20010,"a",#20009) +#20011=* +exprs(#20011,78,#20005,0,"a") +#20012=@"loc,{#10000},1,12,1,12" +locations_default(#20012,#10000,1,12,1,12) +hasLocation(#20011,#20012) +exprContainers(#20011,#20005) +literals("a","a",#20011) +decl(#20011,#20010) +#20013=@"var;{arguments};{#20009}" +variables(#20013,"arguments",#20009) +isArgumentsObject(#20013) +#20014=* +stmts(#20014,1,#20005,-2,"{\n return a;\n}") +#20015=@"loc,{#10000},1,32,3,1" +locations_default(#20015,#10000,1,32,3,1) +hasLocation(#20014,#20015) +stmtContainers(#20014,#20005) +#20016=* +stmts(#20016,9,#20014,0,"return a;") +#20017=@"loc,{#10000},2,5,2,13" +locations_default(#20017,#10000,2,5,2,13) +hasLocation(#20016,#20017) +stmtContainers(#20016,#20005) +#20018=* +exprs(#20018,79,#20016,0,"a") +#20019=@"loc,{#10000},2,12,2,12" +locations_default(#20019,#10000,2,12,2,12) +hasLocation(#20018,#20019) +enclosingStmt(#20018,#20016) +exprContainers(#20018,#20005) +literals("a","a",#20018) +bind(#20018,#20010) +numlines(#20005,3,3,0) +#20020=* +stmts(#20020,17,#20001,1,"functio ... ecks {}") +#20021=@"loc,{#10000},4,1,4,24" +locations_default(#20021,#10000,4,1,4,24) +hasLocation(#20020,#20021) +stmtContainers(#20020,#20001) +#20022=* +exprs(#20022,78,#20020,-1,"g") +#20023=@"loc,{#10000},4,10,4,10" +locations_default(#20023,#10000,4,10,4,10) +hasLocation(#20022,#20023) +exprContainers(#20022,#20020) +literals("g","g",#20022) +decl(#20022,#20004) +#20024=* +scopes(#20024,1) +scopenodes(#20020,#20024) +scopenesting(#20024,#20000) +#20025=@"var;{arguments};{#20024}" +variables(#20025,"arguments",#20024) +isArgumentsObject(#20025) +#20026=* +stmts(#20026,1,#20020,-2,"{}") +#20027=@"loc,{#10000},4,23,4,24" +locations_default(#20027,#10000,4,23,4,24) +hasLocation(#20026,#20027) +stmtContainers(#20026,#20020) +numlines(#20020,1,1,0) +#20028=* +stmts(#20028,1,#20001,2,"{\n return b;\n}") +#20029=@"loc,{#10000},4,26,6,1" +locations_default(#20029,#10000,4,26,6,1) +hasLocation(#20028,#20029) +stmtContainers(#20028,#20001) +#20030=* +stmts(#20030,9,#20028,0,"return b;") +#20031=@"loc,{#10000},5,5,5,13" +locations_default(#20031,#10000,5,5,5,13) +hasLocation(#20030,#20031) +stmtContainers(#20030,#20001) +#20032=* +exprs(#20032,79,#20030,0,"b") +#20033=@"loc,{#10000},5,12,5,12" +locations_default(#20033,#10000,5,12,5,12) +hasLocation(#20032,#20033) +enclosingStmt(#20032,#20030) +exprContainers(#20032,#20001) +literals("b","b",#20032) +#20034=@"var;{b};{#20000}" +variables(#20034,"b",#20000) +bind(#20032,#20034) +#20035=* +stmts(#20035,2,#20001,3,"(c): bo ... s => c;") +#20036=@"loc,{#10000},8,1,8,26" +locations_default(#20036,#10000,8,1,8,26) +hasLocation(#20035,#20036) +stmtContainers(#20035,#20001) +#20037=* +exprs(#20037,65,#20035,0,"(c): bo ... ks => c") +#20038=@"loc,{#10000},8,1,8,25" +locations_default(#20038,#10000,8,1,8,25) +hasLocation(#20037,#20038) +enclosingStmt(#20037,#20035) +exprContainers(#20037,#20001) +#20039=* +scopes(#20039,1) +scopenodes(#20037,#20039) +scopenesting(#20039,#20000) +#20040=@"var;{c};{#20039}" +variables(#20040,"c",#20039) +#20041=* +exprs(#20041,78,#20037,0,"c") +#20042=@"loc,{#10000},8,2,8,2" +locations_default(#20042,#10000,8,2,8,2) +hasLocation(#20041,#20042) +exprContainers(#20041,#20037) +literals("c","c",#20041) +decl(#20041,#20040) +#20043=* +exprs(#20043,79,#20037,-2,"c") +#20044=@"loc,{#10000},8,25,8,25" +locations_default(#20044,#10000,8,25,8,25) +hasLocation(#20043,#20044) +exprContainers(#20043,#20037) +literals("c","c",#20043) +bind(#20043,#20040) +numlines(#20037,1,1,0) +#20045=* +stmts(#20045,2,#20001,4,"(d): %checks => d;") +#20046=@"loc,{#10000},9,1,9,18" +locations_default(#20046,#10000,9,1,9,18) +hasLocation(#20045,#20046) +stmtContainers(#20045,#20001) +#20047=* +exprs(#20047,65,#20045,0,"(d): %checks => d") +#20048=@"loc,{#10000},9,1,9,17" +locations_default(#20048,#10000,9,1,9,17) +hasLocation(#20047,#20048) +enclosingStmt(#20047,#20045) +exprContainers(#20047,#20001) +#20049=* +scopes(#20049,1) +scopenodes(#20047,#20049) +scopenesting(#20049,#20000) +#20050=@"var;{d};{#20049}" +variables(#20050,"d",#20049) +#20051=* +exprs(#20051,78,#20047,0,"d") +#20052=@"loc,{#10000},9,2,9,2" +locations_default(#20052,#10000,9,2,9,2) +hasLocation(#20051,#20052) +exprContainers(#20051,#20047) +literals("d","d",#20051) +decl(#20051,#20050) +#20053=* +exprs(#20053,79,#20047,-2,"d") +#20054=@"loc,{#10000},9,17,9,17" +locations_default(#20054,#10000,9,17,9,17) +hasLocation(#20053,#20054) +exprContainers(#20053,#20047) +literals("d","d",#20053) +bind(#20053,#20050) +numlines(#20047,1,1,0) +#20055=* +lines(#20055,#20001,"function f(a): boolean %checks {"," +") +#20056=@"loc,{#10000},1,1,1,32" +locations_default(#20056,#10000,1,1,1,32) +hasLocation(#20055,#20056) +#20057=* +lines(#20057,#20001," return a;"," +") +#20058=@"loc,{#10000},2,1,2,13" +locations_default(#20058,#10000,2,1,2,13) +hasLocation(#20057,#20058) +indentation(#10000,2," ",4) +#20059=* +lines(#20059,#20001,"}"," +") +#20060=@"loc,{#10000},3,1,3,1" +locations_default(#20060,#10000,3,1,3,1) +hasLocation(#20059,#20060) +#20061=* +lines(#20061,#20001,"function g(): %checks {} {"," +") +#20062=@"loc,{#10000},4,1,4,26" +locations_default(#20062,#10000,4,1,4,26) +hasLocation(#20061,#20062) +#20063=* +lines(#20063,#20001," return b;"," +") +#20064=@"loc,{#10000},5,1,5,13" +locations_default(#20064,#10000,5,1,5,13) +hasLocation(#20063,#20064) +indentation(#10000,5," ",4) +#20065=* +lines(#20065,#20001,"}"," +") +#20066=@"loc,{#10000},6,1,6,1" +locations_default(#20066,#10000,6,1,6,1) +hasLocation(#20065,#20066) +#20067=* +lines(#20067,#20001,""," +") +#20068=@"loc,{#10000},7,1,7,0" +locations_default(#20068,#10000,7,1,7,0) +hasLocation(#20067,#20068) +#20069=* +lines(#20069,#20001,"(c): boolean %checks => c;"," +") +hasLocation(#20069,#20036) +#20070=* +lines(#20070,#20001,"(d): %checks => d;"," +") +hasLocation(#20070,#20046) +numlines(#20001,9,8,0) +#20071=* +tokeninfo(#20071,7,#20001,0,"function") +#20072=@"loc,{#10000},1,1,1,8" +locations_default(#20072,#10000,1,1,1,8) +hasLocation(#20071,#20072) +#20073=* +tokeninfo(#20073,6,#20001,1,"f") +hasLocation(#20073,#20008) +#20074=* +tokeninfo(#20074,8,#20001,2,"(") +#20075=@"loc,{#10000},1,11,1,11" +locations_default(#20075,#10000,1,11,1,11) +hasLocation(#20074,#20075) +#20076=* +tokeninfo(#20076,6,#20001,3,"a") +hasLocation(#20076,#20012) +#20077=* +tokeninfo(#20077,8,#20001,4,")") +#20078=@"loc,{#10000},1,13,1,13" +locations_default(#20078,#10000,1,13,1,13) +hasLocation(#20077,#20078) +#20079=* +tokeninfo(#20079,8,#20001,5,":") +#20080=@"loc,{#10000},1,14,1,14" +locations_default(#20080,#10000,1,14,1,14) +hasLocation(#20079,#20080) +#20081=* +tokeninfo(#20081,6,#20001,6,"boolean") +#20082=@"loc,{#10000},1,16,1,22" +locations_default(#20082,#10000,1,16,1,22) +hasLocation(#20081,#20082) +#20083=* +tokeninfo(#20083,8,#20001,7,"%") +#20084=@"loc,{#10000},1,24,1,24" +locations_default(#20084,#10000,1,24,1,24) +hasLocation(#20083,#20084) +#20085=* +tokeninfo(#20085,6,#20001,8,"checks") +#20086=@"loc,{#10000},1,25,1,30" +locations_default(#20086,#10000,1,25,1,30) +hasLocation(#20085,#20086) +#20087=* +tokeninfo(#20087,8,#20001,9,"{") +#20088=@"loc,{#10000},1,32,1,32" +locations_default(#20088,#10000,1,32,1,32) +hasLocation(#20087,#20088) +#20089=* +tokeninfo(#20089,7,#20001,10,"return") +#20090=@"loc,{#10000},2,5,2,10" +locations_default(#20090,#10000,2,5,2,10) +hasLocation(#20089,#20090) +#20091=* +tokeninfo(#20091,6,#20001,11,"a") +hasLocation(#20091,#20019) +#20092=* +tokeninfo(#20092,8,#20001,12,";") +#20093=@"loc,{#10000},2,13,2,13" +locations_default(#20093,#10000,2,13,2,13) +hasLocation(#20092,#20093) +#20094=* +tokeninfo(#20094,8,#20001,13,"}") +hasLocation(#20094,#20060) +#20095=* +tokeninfo(#20095,7,#20001,14,"function") +#20096=@"loc,{#10000},4,1,4,8" +locations_default(#20096,#10000,4,1,4,8) +hasLocation(#20095,#20096) +#20097=* +tokeninfo(#20097,6,#20001,15,"g") +hasLocation(#20097,#20023) +#20098=* +tokeninfo(#20098,8,#20001,16,"(") +#20099=@"loc,{#10000},4,11,4,11" +locations_default(#20099,#10000,4,11,4,11) +hasLocation(#20098,#20099) +#20100=* +tokeninfo(#20100,8,#20001,17,")") +#20101=@"loc,{#10000},4,12,4,12" +locations_default(#20101,#10000,4,12,4,12) +hasLocation(#20100,#20101) +#20102=* +tokeninfo(#20102,8,#20001,18,":") +#20103=@"loc,{#10000},4,13,4,13" +locations_default(#20103,#10000,4,13,4,13) +hasLocation(#20102,#20103) +#20104=* +tokeninfo(#20104,8,#20001,19,"%") +#20105=@"loc,{#10000},4,15,4,15" +locations_default(#20105,#10000,4,15,4,15) +hasLocation(#20104,#20105) +#20106=* +tokeninfo(#20106,6,#20001,20,"checks") +#20107=@"loc,{#10000},4,16,4,21" +locations_default(#20107,#10000,4,16,4,21) +hasLocation(#20106,#20107) +#20108=* +tokeninfo(#20108,8,#20001,21,"{") +#20109=@"loc,{#10000},4,23,4,23" +locations_default(#20109,#10000,4,23,4,23) +hasLocation(#20108,#20109) +#20110=* +tokeninfo(#20110,8,#20001,22,"}") +#20111=@"loc,{#10000},4,24,4,24" +locations_default(#20111,#10000,4,24,4,24) +hasLocation(#20110,#20111) +#20112=* +tokeninfo(#20112,8,#20001,23,"{") +#20113=@"loc,{#10000},4,26,4,26" +locations_default(#20113,#10000,4,26,4,26) +hasLocation(#20112,#20113) +#20114=* +tokeninfo(#20114,7,#20001,24,"return") +#20115=@"loc,{#10000},5,5,5,10" +locations_default(#20115,#10000,5,5,5,10) +hasLocation(#20114,#20115) +#20116=* +tokeninfo(#20116,6,#20001,25,"b") +hasLocation(#20116,#20033) +#20117=* +tokeninfo(#20117,8,#20001,26,";") +#20118=@"loc,{#10000},5,13,5,13" +locations_default(#20118,#10000,5,13,5,13) +hasLocation(#20117,#20118) +#20119=* +tokeninfo(#20119,8,#20001,27,"}") +hasLocation(#20119,#20066) +#20120=* +tokeninfo(#20120,8,#20001,28,"(") +#20121=@"loc,{#10000},8,1,8,1" +locations_default(#20121,#10000,8,1,8,1) +hasLocation(#20120,#20121) +#20122=* +tokeninfo(#20122,6,#20001,29,"c") +hasLocation(#20122,#20042) +#20123=* +tokeninfo(#20123,8,#20001,30,")") +#20124=@"loc,{#10000},8,3,8,3" +locations_default(#20124,#10000,8,3,8,3) +hasLocation(#20123,#20124) +#20125=* +tokeninfo(#20125,8,#20001,31,":") +#20126=@"loc,{#10000},8,4,8,4" +locations_default(#20126,#10000,8,4,8,4) +hasLocation(#20125,#20126) +#20127=* +tokeninfo(#20127,6,#20001,32,"boolean") +#20128=@"loc,{#10000},8,6,8,12" +locations_default(#20128,#10000,8,6,8,12) +hasLocation(#20127,#20128) +#20129=* +tokeninfo(#20129,8,#20001,33,"%") +#20130=@"loc,{#10000},8,14,8,14" +locations_default(#20130,#10000,8,14,8,14) +hasLocation(#20129,#20130) +#20131=* +tokeninfo(#20131,6,#20001,34,"checks") +#20132=@"loc,{#10000},8,15,8,20" +locations_default(#20132,#10000,8,15,8,20) +hasLocation(#20131,#20132) +#20133=* +tokeninfo(#20133,8,#20001,35,"=>") +#20134=@"loc,{#10000},8,22,8,23" +locations_default(#20134,#10000,8,22,8,23) +hasLocation(#20133,#20134) +#20135=* +tokeninfo(#20135,6,#20001,36,"c") +hasLocation(#20135,#20044) +#20136=* +tokeninfo(#20136,8,#20001,37,";") +#20137=@"loc,{#10000},8,26,8,26" +locations_default(#20137,#10000,8,26,8,26) +hasLocation(#20136,#20137) +#20138=* +tokeninfo(#20138,8,#20001,38,"(") +#20139=@"loc,{#10000},9,1,9,1" +locations_default(#20139,#10000,9,1,9,1) +hasLocation(#20138,#20139) +#20140=* +tokeninfo(#20140,6,#20001,39,"d") +hasLocation(#20140,#20052) +#20141=* +tokeninfo(#20141,8,#20001,40,")") +#20142=@"loc,{#10000},9,3,9,3" +locations_default(#20142,#10000,9,3,9,3) +hasLocation(#20141,#20142) +#20143=* +tokeninfo(#20143,8,#20001,41,":") +#20144=@"loc,{#10000},9,4,9,4" +locations_default(#20144,#10000,9,4,9,4) +hasLocation(#20143,#20144) +#20145=* +tokeninfo(#20145,8,#20001,42,"%") +#20146=@"loc,{#10000},9,6,9,6" +locations_default(#20146,#10000,9,6,9,6) +hasLocation(#20145,#20146) +#20147=* +tokeninfo(#20147,6,#20001,43,"checks") +#20148=@"loc,{#10000},9,7,9,12" +locations_default(#20148,#10000,9,7,9,12) +hasLocation(#20147,#20148) +#20149=* +tokeninfo(#20149,8,#20001,44,"=>") +#20150=@"loc,{#10000},9,14,9,15" +locations_default(#20150,#10000,9,14,9,15) +hasLocation(#20149,#20150) +#20151=* +tokeninfo(#20151,6,#20001,45,"d") +hasLocation(#20151,#20054) +#20152=* +tokeninfo(#20152,8,#20001,46,";") +#20153=@"loc,{#10000},9,18,9,18" +locations_default(#20153,#10000,9,18,9,18) +hasLocation(#20152,#20153) +#20154=* +tokeninfo(#20154,0,#20001,47,"") +#20155=@"loc,{#10000},10,1,10,0" +locations_default(#20155,#10000,10,1,10,0) +hasLocation(#20154,#20155) +#20156=* +entry_cfg_node(#20156,#20001) +#20157=@"loc,{#10000},1,1,1,0" +locations_default(#20157,#10000,1,1,1,0) +hasLocation(#20156,#20157) +#20158=* +exit_cfg_node(#20158,#20001) +hasLocation(#20158,#20155) +successor(#20045,#20047) +successor(#20047,#20158) +#20159=* +entry_cfg_node(#20159,#20047) +#20160=@"loc,{#10000},9,1,9,0" +locations_default(#20160,#10000,9,1,9,0) +hasLocation(#20159,#20160) +#20161=* +exit_cfg_node(#20161,#20047) +#20162=@"loc,{#10000},9,18,9,17" +locations_default(#20162,#10000,9,18,9,17) +hasLocation(#20161,#20162) +successor(#20053,#20161) +successor(#20051,#20053) +successor(#20159,#20051) +successor(#20035,#20037) +successor(#20037,#20045) +#20163=* +entry_cfg_node(#20163,#20037) +#20164=@"loc,{#10000},8,1,8,0" +locations_default(#20164,#10000,8,1,8,0) +hasLocation(#20163,#20164) +#20165=* +exit_cfg_node(#20165,#20037) +#20166=@"loc,{#10000},8,26,8,25" +locations_default(#20166,#10000,8,26,8,25) +hasLocation(#20165,#20166) +successor(#20043,#20165) +successor(#20041,#20043) +successor(#20163,#20041) +successor(#20028,#20032) +successor(#20032,#20030) +successor(#20030,#20158) +successor(#20020,#20028) +#20167=* +entry_cfg_node(#20167,#20020) +#20168=@"loc,{#10000},4,1,4,0" +locations_default(#20168,#10000,4,1,4,0) +hasLocation(#20167,#20168) +#20169=* +exit_cfg_node(#20169,#20020) +#20170=@"loc,{#10000},4,25,4,24" +locations_default(#20170,#10000,4,25,4,24) +hasLocation(#20169,#20170) +successor(#20026,#20169) +successor(#20167,#20026) +successor(#20005,#20020) +#20171=* +entry_cfg_node(#20171,#20005) +hasLocation(#20171,#20157) +#20172=* +exit_cfg_node(#20172,#20005) +#20173=@"loc,{#10000},3,2,3,1" +locations_default(#20173,#10000,3,2,3,1) +hasLocation(#20172,#20173) +successor(#20014,#20018) +successor(#20018,#20016) +successor(#20016,#20172) +successor(#20011,#20014) +successor(#20171,#20011) +successor(#20022,#20005) +successor(#20007,#20022) +successor(#20156,#20007) +numlines(#10000,9,8,0) +filetype(#10000,"javascript") diff --git a/javascript/extractor/tests/shebang/input/not-typescript.ts b/javascript/extractor/tests/shebang/input/not-typescript.ts new file mode 100644 index 000000000000..6953faca5164 --- /dev/null +++ b/javascript/extractor/tests/shebang/input/not-typescript.ts @@ -0,0 +1,5 @@ +#!/usr/bin/env perl + +use strict; + +exit 0; diff --git a/javascript/extractor/tests/shebang/input/typescript-with-shebang.ts b/javascript/extractor/tests/shebang/input/typescript-with-shebang.ts new file mode 100644 index 000000000000..bed2eaa1fbb9 --- /dev/null +++ b/javascript/extractor/tests/shebang/input/typescript-with-shebang.ts @@ -0,0 +1,4 @@ +#!/usr/bin/env node +interface Foo { + x: number; +} diff --git a/javascript/extractor/tests/shebang/input/typescript.ts b/javascript/extractor/tests/shebang/input/typescript.ts new file mode 100644 index 000000000000..a9a86d5ce041 --- /dev/null +++ b/javascript/extractor/tests/shebang/input/typescript.ts @@ -0,0 +1,3 @@ +interface Foo { + x: number; +} diff --git a/javascript/extractor/tests/shebang/options.json b/javascript/extractor/tests/shebang/options.json new file mode 100644 index 000000000000..cdf22d686fd7 --- /dev/null +++ b/javascript/extractor/tests/shebang/options.json @@ -0,0 +1,3 @@ +{ + "typescript": true +} diff --git a/javascript/extractor/tests/shebang/output/trap/typescript-with-shebang.ts.trap b/javascript/extractor/tests/shebang/output/trap/typescript-with-shebang.ts.trap new file mode 100644 index 000000000000..cd7804866b88 --- /dev/null +++ b/javascript/extractor/tests/shebang/output/trap/typescript-with-shebang.ts.trap @@ -0,0 +1,129 @@ +#10000=@"/typescript-with-shebang.ts;sourcefile" +files(#10000,"/typescript-with-shebang.ts","typescript-with-shebang","ts",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,5,0" +locations_default(#20002,#10000,1,1,5,0) +hasLocation(#20001,#20002) +#20003=@"local_type_name;{Foo};{#20000}" +local_type_names(#20003,"Foo",#20000) +#20004=* +stmts(#20004,34,#20001,0,"#!/usr/ ... mber;\n}") +#20005=@"loc,{#10000},1,1,4,1" +locations_default(#20005,#10000,1,1,4,1) +hasLocation(#20004,#20005) +stmtContainers(#20004,#20001) +#20006=* +typeexprs(#20006,1,#20004,0,"Foo") +#20007=@"loc,{#10000},2,11,2,13" +locations_default(#20007,#10000,2,11,2,13) +hasLocation(#20006,#20007) +enclosingStmt(#20006,#20004) +exprContainers(#20006,#20001) +literals("Foo","Foo",#20006) +typedecl(#20006,#20003) +#20008=* +properties(#20008,#20004,2,8,"x: number;") +#20009=@"loc,{#10000},3,3,3,12" +locations_default(#20009,#10000,3,3,3,12) +hasLocation(#20008,#20009) +#20010=* +exprs(#20010,0,#20008,0,"x") +#20011=@"loc,{#10000},3,3,3,3" +locations_default(#20011,#10000,3,3,3,3) +hasLocation(#20010,#20011) +enclosingStmt(#20010,#20004) +exprContainers(#20010,#20001) +literals("x","x",#20010) +isAbstractMember(#20008) +#20012=* +typeexprs(#20012,2,#20008,2,"number") +#20013=@"loc,{#10000},3,6,3,11" +locations_default(#20013,#10000,3,6,3,11) +hasLocation(#20012,#20013) +enclosingStmt(#20012,#20004) +exprContainers(#20012,#20001) +literals("number","number",#20012) +#20014=* +lines(#20014,#20001,"#!/usr/bin/env node"," +") +#20015=@"loc,{#10000},1,1,1,19" +locations_default(#20015,#10000,1,1,1,19) +hasLocation(#20014,#20015) +#20016=* +lines(#20016,#20001,"interface Foo {"," +") +#20017=@"loc,{#10000},2,1,2,15" +locations_default(#20017,#10000,2,1,2,15) +hasLocation(#20016,#20017) +#20018=* +lines(#20018,#20001," x: number;"," +") +#20019=@"loc,{#10000},3,1,3,12" +locations_default(#20019,#10000,3,1,3,12) +hasLocation(#20018,#20019) +indentation(#10000,3," ",2) +#20020=* +lines(#20020,#20001,"}"," +") +#20021=@"loc,{#10000},4,1,4,1" +locations_default(#20021,#10000,4,1,4,1) +hasLocation(#20020,#20021) +numlines(#20001,4,3,0) +#20022=* +tokeninfo(#20022,7,#20001,0,"interface") +#20023=@"loc,{#10000},2,1,2,9" +locations_default(#20023,#10000,2,1,2,9) +hasLocation(#20022,#20023) +#20024=* +tokeninfo(#20024,6,#20001,1,"Foo") +hasLocation(#20024,#20007) +#20025=* +tokeninfo(#20025,8,#20001,2,"{") +#20026=@"loc,{#10000},2,15,2,15" +locations_default(#20026,#10000,2,15,2,15) +hasLocation(#20025,#20026) +#20027=* +tokeninfo(#20027,6,#20001,3,"x") +hasLocation(#20027,#20011) +#20028=* +tokeninfo(#20028,8,#20001,4,":") +#20029=@"loc,{#10000},3,4,3,4" +locations_default(#20029,#10000,3,4,3,4) +hasLocation(#20028,#20029) +#20030=* +tokeninfo(#20030,7,#20001,5,"number") +hasLocation(#20030,#20013) +#20031=* +tokeninfo(#20031,8,#20001,6,";") +#20032=@"loc,{#10000},3,12,3,12" +locations_default(#20032,#10000,3,12,3,12) +hasLocation(#20031,#20032) +#20033=* +tokeninfo(#20033,8,#20001,7,"}") +hasLocation(#20033,#20021) +#20034=* +tokeninfo(#20034,0,#20001,8,"") +#20035=@"loc,{#10000},5,1,5,0" +locations_default(#20035,#10000,5,1,5,0) +hasLocation(#20034,#20035) +#20036=* +entry_cfg_node(#20036,#20001) +#20037=@"loc,{#10000},1,1,1,0" +locations_default(#20037,#10000,1,1,1,0) +hasLocation(#20036,#20037) +#20038=* +exit_cfg_node(#20038,#20001) +hasLocation(#20038,#20035) +successor(#20004,#20038) +successor(#20036,#20004) +numlines(#10000,4,3,0) +filetype(#10000,"typescript") diff --git a/javascript/extractor/tests/shebang/output/trap/typescript.ts.trap b/javascript/extractor/tests/shebang/output/trap/typescript.ts.trap new file mode 100644 index 000000000000..936f5ce0896a --- /dev/null +++ b/javascript/extractor/tests/shebang/output/trap/typescript.ts.trap @@ -0,0 +1,123 @@ +#10000=@"/typescript.ts;sourcefile" +files(#10000,"/typescript.ts","typescript","ts",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +toplevels(#20001,0) +#20002=@"loc,{#10000},1,1,4,0" +locations_default(#20002,#10000,1,1,4,0) +hasLocation(#20001,#20002) +#20003=@"local_type_name;{Foo};{#20000}" +local_type_names(#20003,"Foo",#20000) +#20004=* +stmts(#20004,34,#20001,0,"interfa ... mber;\n}") +#20005=@"loc,{#10000},1,1,3,1" +locations_default(#20005,#10000,1,1,3,1) +hasLocation(#20004,#20005) +stmtContainers(#20004,#20001) +#20006=* +typeexprs(#20006,1,#20004,0,"Foo") +#20007=@"loc,{#10000},1,11,1,13" +locations_default(#20007,#10000,1,11,1,13) +hasLocation(#20006,#20007) +enclosingStmt(#20006,#20004) +exprContainers(#20006,#20001) +literals("Foo","Foo",#20006) +typedecl(#20006,#20003) +#20008=* +properties(#20008,#20004,2,8,"x: number;") +#20009=@"loc,{#10000},2,3,2,12" +locations_default(#20009,#10000,2,3,2,12) +hasLocation(#20008,#20009) +#20010=* +exprs(#20010,0,#20008,0,"x") +#20011=@"loc,{#10000},2,3,2,3" +locations_default(#20011,#10000,2,3,2,3) +hasLocation(#20010,#20011) +enclosingStmt(#20010,#20004) +exprContainers(#20010,#20001) +literals("x","x",#20010) +isAbstractMember(#20008) +#20012=* +typeexprs(#20012,2,#20008,2,"number") +#20013=@"loc,{#10000},2,6,2,11" +locations_default(#20013,#10000,2,6,2,11) +hasLocation(#20012,#20013) +enclosingStmt(#20012,#20004) +exprContainers(#20012,#20001) +literals("number","number",#20012) +#20014=* +lines(#20014,#20001,"interface Foo {"," +") +#20015=@"loc,{#10000},1,1,1,15" +locations_default(#20015,#10000,1,1,1,15) +hasLocation(#20014,#20015) +#20016=* +lines(#20016,#20001," x: number;"," +") +#20017=@"loc,{#10000},2,1,2,12" +locations_default(#20017,#10000,2,1,2,12) +hasLocation(#20016,#20017) +indentation(#10000,2," ",2) +#20018=* +lines(#20018,#20001,"}"," +") +#20019=@"loc,{#10000},3,1,3,1" +locations_default(#20019,#10000,3,1,3,1) +hasLocation(#20018,#20019) +numlines(#20001,3,3,0) +#20020=* +tokeninfo(#20020,7,#20001,0,"interface") +#20021=@"loc,{#10000},1,1,1,9" +locations_default(#20021,#10000,1,1,1,9) +hasLocation(#20020,#20021) +#20022=* +tokeninfo(#20022,6,#20001,1,"Foo") +hasLocation(#20022,#20007) +#20023=* +tokeninfo(#20023,8,#20001,2,"{") +#20024=@"loc,{#10000},1,15,1,15" +locations_default(#20024,#10000,1,15,1,15) +hasLocation(#20023,#20024) +#20025=* +tokeninfo(#20025,6,#20001,3,"x") +hasLocation(#20025,#20011) +#20026=* +tokeninfo(#20026,8,#20001,4,":") +#20027=@"loc,{#10000},2,4,2,4" +locations_default(#20027,#10000,2,4,2,4) +hasLocation(#20026,#20027) +#20028=* +tokeninfo(#20028,7,#20001,5,"number") +hasLocation(#20028,#20013) +#20029=* +tokeninfo(#20029,8,#20001,6,";") +#20030=@"loc,{#10000},2,12,2,12" +locations_default(#20030,#10000,2,12,2,12) +hasLocation(#20029,#20030) +#20031=* +tokeninfo(#20031,8,#20001,7,"}") +hasLocation(#20031,#20019) +#20032=* +tokeninfo(#20032,0,#20001,8,"") +#20033=@"loc,{#10000},4,1,4,0" +locations_default(#20033,#10000,4,1,4,0) +hasLocation(#20032,#20033) +#20034=* +entry_cfg_node(#20034,#20001) +#20035=@"loc,{#10000},1,1,1,0" +locations_default(#20035,#10000,1,1,1,0) +hasLocation(#20034,#20035) +#20036=* +exit_cfg_node(#20036,#20001) +hasLocation(#20036,#20033) +successor(#20004,#20036) +successor(#20034,#20004) +numlines(#10000,3,3,0) +filetype(#10000,"typescript") diff --git a/javascript/ql/src/Expressions/SuspiciousInvocation.ql b/javascript/ql/src/Expressions/SuspiciousInvocation.ql index 1f1961ab4456..902fbd5abdae 100644 --- a/javascript/ql/src/Expressions/SuspiciousInvocation.ql +++ b/javascript/ql/src/Expressions/SuspiciousInvocation.ql @@ -16,5 +16,6 @@ private import semmle.javascript.dataflow.InferredTypes from InvokeExpr invk, DataFlow::AnalyzedNode callee where callee.asExpr() = invk.getCallee() and forex (InferredType tp | tp = callee.getAType() | tp != TTFunction() and tp != TTClass()) and - not invk.isAmbient() + not invk.isAmbient() and + not invk instanceof OptionalUse select invk, "Callee is not a function: it has type " + callee.ppTypes() + "." \ No newline at end of file diff --git a/javascript/ql/src/Expressions/SuspiciousPropAccess.ql b/javascript/ql/src/Expressions/SuspiciousPropAccess.ql index 2b81203d3c08..946c6d78418c 100644 --- a/javascript/ql/src/Expressions/SuspiciousPropAccess.ql +++ b/javascript/ql/src/Expressions/SuspiciousPropAccess.ql @@ -32,5 +32,6 @@ from PropAccess pacc, DataFlow::AnalyzedNode base where base.asExpr() = pacc.getBase() and forex (InferredType tp | tp = base.getAType() | tp = TTNull() or tp = TTUndefined()) and not namespaceOrConstEnumAccess(pacc.getBase()) and - not pacc.isAmbient() + not pacc.isAmbient() and + not pacc instanceof OptionalUse select pacc, "The base expression of this property access is always " + base.ppTypes() + "." diff --git a/javascript/ql/src/META-INF/MANIFEST.MF b/javascript/ql/src/META-INF/MANIFEST.MF deleted file mode 100644 index 4313cac94f7a..000000000000 --- a/javascript/ql/src/META-INF/MANIFEST.MF +++ /dev/null @@ -1,8 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: Semmle JavaScript Default Queries -Bundle-SymbolicName: com.semmle.plugin.semmlecode.javascript.queries;singleton:=true -Bundle-Version: 1.18.3.qualifier -Bundle-Vendor: Semmle Ltd. -Bundle-ActivationPolicy: lazy -Require-Bundle: com.semmle.plugin.qdt.ui;bundle-version="[1.18.3.qualifier,1.18.3.qualifier]" diff --git a/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.qhelp b/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.qhelp new file mode 100644 index 000000000000..c655b2b78814 --- /dev/null +++ b/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.qhelp @@ -0,0 +1,53 @@ + + + + +

    +Calling a user-controlled method on certain objects can lead to invocation of unsafe functions, +such as eval or the Function constructor. In particular, the global object +contains the eval function, and any function object contains the Function constructor +in its constructor property. +

    +
    + + +

    +Avoid invoking user-controlled methods on the global object or on any function object. +Whitelist the permitted method names or change the type of object the methods are stored on. +

    +
    + + +

    +In the following example, a message from the document's parent frame can invoke the play +or pause method. However, it can also invoke eval. +A malicious website could embed the page in an iframe and execute arbitrary code by sending a message +with the name eval. +

    + + + +

    +Instead of storing the API methods in the global scope, put them in an API object or Map. It is also good +practice to prevent invocation of inherited methods like toString and valueOf. +

    + + + +
    + + +
  • +OWASP: +Code Injection. +
  • +
  • +MDN: Global functions. +
  • +
  • +MDN: Function constructor. +
  • +
    +
    diff --git a/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.ql b/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.ql new file mode 100644 index 000000000000..b0715177412c --- /dev/null +++ b/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.ql @@ -0,0 +1,17 @@ +/** + * @name Unsafe dynamic method access + * @description Invoking user-controlled methods on certain objects can lead to remote code execution. + * @kind path-problem + * @problem.severity error + * @precision high + * @id js/unsafe-dynamic-method-access + * @tags security + * external/cwe/cwe-094 + */ +import javascript +import semmle.javascript.security.dataflow.UnsafeDynamicMethodAccess::UnsafeDynamicMethodAccess +import DataFlow::PathGraph + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink, source, sink, "Invocation of method derived from $@ may lead to remote code execution.", source.getNode(), "user-controlled value" diff --git a/javascript/ql/src/Security/CWE-094/examples/UnsafeDynamicMethodAccess.js b/javascript/ql/src/Security/CWE-094/examples/UnsafeDynamicMethodAccess.js new file mode 100644 index 000000000000..ec4a3a8d9d71 --- /dev/null +++ b/javascript/ql/src/Security/CWE-094/examples/UnsafeDynamicMethodAccess.js @@ -0,0 +1,14 @@ +// API methods +function play(data) { + // ... +} +function pause(data) { + // ... +} + +window.addEventListener("message", (ev) => { + let message = JSON.parse(ev.data); + + // Let the parent frame call the 'play' or 'pause' function + window[message.name](message.payload); +}); diff --git a/javascript/ql/src/Security/CWE-094/examples/UnsafeDynamicMethodAccessGood.js b/javascript/ql/src/Security/CWE-094/examples/UnsafeDynamicMethodAccessGood.js new file mode 100644 index 000000000000..1636507ba3bd --- /dev/null +++ b/javascript/ql/src/Security/CWE-094/examples/UnsafeDynamicMethodAccessGood.js @@ -0,0 +1,19 @@ +// API methods +let api = { + play: function(data) { + // ... + }, + pause: function(data) { + // ... + } +}; + +window.addEventListener("message", (ev) => { + let message = JSON.parse(ev.data); + + // Let the parent frame call the 'play' or 'pause' function + if (!api.hasOwnProperty(message.name)) { + return; + } + api[message.name](message.payload); +}); diff --git a/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.qhelp b/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.qhelp index bfdd32b645e8..c93ab4d09f6c 100644 --- a/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.qhelp +++ b/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.qhelp @@ -6,31 +6,22 @@

    Dynamically computing object property names from untrusted input - may have multiple undesired consequences. For example, - if the property access is used as part of a write, an - attacker may overwrite vital properties of objects, such as - __proto__. This attack is known as prototype - pollution attack and may serve as a vehicle for denial-of-service - attacks. A similar attack vector, is to replace the - toString property of an object with a primitive. - Whenever toString is then called on that object, either - explicitly or implicitly as part of a type coercion, an exception + may have multiple undesired consequences. For example, + if the property access is used as part of a write, an + attacker may overwrite vital properties of objects, such as + __proto__. This attack is known as prototype + pollution attack and may serve as a vehicle for denial-of-service + attacks. A similar attack vector, is to replace the + toString property of an object with a primitive. + Whenever toString is then called on that object, either + explicitly or implicitly as part of a type coercion, an exception will be raised.

    - Moreover, if the dynamically computed property is - used as part of a method call, the attacker may trigger - the execution of unwanted functions such as the - Function constructor or the - eval method, which can be used - for code injection. -

    - -

    - Additionally, if the name of an HTTP header is user-controlled, - an attacker may exploit this to overwrite security-critical headers - such as Access-Control-Allow-Origin or + Moreover, if the name of an HTTP header is user-controlled, + an attacker may exploit this to overwrite security-critical headers + such as Access-Control-Allow-Origin or Content-Security-Policy.

    @@ -38,57 +29,57 @@

    The most common case in which prototype pollution vulnerabilities arise - is when JavaScript objects are used for implementing map data - structures. This case should be avoided whenever possible by using the - ECMAScript 2015 Map instead. When this is not possible, an - alternative fix is to prepend untrusted input with a marker character - such as $, before using it in properties accesses. In this way, - the attacker does not have access to built-in properties which do not - start with the chosen character. + is when JavaScript objects are used for implementing map data + structures. This case should be avoided whenever possible by using the + ECMAScript 2015 Map instead. When this is not possible, an + alternative fix is to prepend untrusted input with a marker character + such as $, before using it in properties accesses. In this way, + the attacker does not have access to built-in properties which do not + start with the chosen character.

    - When using user input as part of header or method names, a sanitization - step should be performed on the input to ensure that the name does not - clash with existing property and header names such as - __proto__ or Content-Security-Policy. + When using user input as part of a header name, a sanitization + step should be performed on the input to ensure that the name does not + clash with existing header names such as + Content-Security-Policy.

    - In the example below, the dynamically computed property - prop is accessed on myObj using a + In the example below, the dynamically computed property + prop is accessed on myObj using a user-controlled value.

    - This is not secure since an attacker may exploit this code to + This is not secure since an attacker may exploit this code to overwrite the property __proto__ with an empty function. - If this happens, the concatenation in the console.log - argument will fail with a confusing message such as + If this happens, the concatenation in the console.log + argument will fail with a confusing message such as "Function.prototype.toString is not generic". If the application does not properly handle this error, this scenario may result in a serious - denial-of-service attack. The fix is to prepend the user-controlled - string with a marker character such as $ which will - prevent arbitrary property names from being overwritten. + denial-of-service attack. The fix is to prepend the user-controlled + string with a marker character such as $ which will + prevent arbitrary property names from being overwritten.

    -
  • Prototype pollution attacks: +
  • Prototype pollution attacks: electron, lodash, hoek.
  • -
  • Penetration testing report: +
  • Penetration testing report: header name injection attack
  • -
  • npm blog post: +
  • npm blog post: dangers of square bracket notation
  • diff --git a/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql b/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql index 4457899f6b33..1dafa5b2dfcf 100644 --- a/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql +++ b/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql @@ -1,8 +1,7 @@ /** * @name Remote property injection - * @description Allowing writes to arbitrary properties or calls to arbitrary - * methods of an object may lead to denial-of-service attacks. - * + * @description Allowing writes to arbitrary properties of an object may lead to + * denial-of-service attacks. * @kind path-problem * @problem.severity warning * @precision medium diff --git a/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.qhelp b/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.qhelp new file mode 100644 index 000000000000..14a2e49ee1dc --- /dev/null +++ b/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.qhelp @@ -0,0 +1,86 @@ + + + + +

    +JavaScript makes it easy to look up object properties dynamically at runtime. In particular, methods +can be looked up by name and then called. However, if he method name is user controlled, an attacker +could choose a name that makes the application invoke an unexpected method, which may cause a runtime +exception. If this exception is not handled, it could be used to mount a denial-of-service attack. +

    +

    +For example, there might not be a method of the given name or the result of the lookup might not be +a function, which would cause the method call to throw a TypeError at runtime. +

    +

    +Another, more subtle example is where the result of the lookup is a standard library method from +Object.prototype, which most objects have on their prototype chain. Examples of such +methods include valueOf, hasOwnProperty and __defineSetter__. +If the method call passes the wrong number or kind of arguments to these methods, they will +throw an exception. +

    +
    + + +

    +It is best to avoid dynamic method lookup involving user-controlled names altogether, for instance +by using a Map instead of a plain object. +

    +

    +If the dynamic method lookup cannot be avoided, consider whitelisting permitted method names. At +the very least, check that the method is an own property and not inherited from the prototype object. +If the object on which the method is looked up contains properties that are not methods, you +should additionally check that the result of the lookup is a function. Even if the object only +contains methods it is still a good idea to perform this check in case other properties are +added to the object later on. +

    +
    + + +

    +In the following example, an HTTP request parameter action property is used to dynamically +look up a function in the actions map, which is then invoked with the payload +parameter as its argument. +

    + + + +

    +The intention is to allow clients to invoke the play or pause method, but there +is no check that action is actually the name of a method stored in actions. +If, for example, action is rewind, action will be undefined +and the call will result in a runtime error. +

    + +

    +The easiest way to prevent this is to turn actions into a Map and using +Map.prototype.has to check whether the method name is valid before looking it up. +

    + + + +

    +If actions cannot be turned into a Map, a hasOwnProperty +check should be added to validate the method name: +

    + + + +
    + + +
  • +OWASP: +Denial of Service. +
  • +
  • +MDN: Map. +
  • +
  • +MDN: Object.prototype. +
  • +
    + +
    diff --git a/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.ql b/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.ql new file mode 100644 index 000000000000..df3b2e8aa162 --- /dev/null +++ b/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.ql @@ -0,0 +1,21 @@ +/** + * @name Unvalidated dynamic method call + * @description Calling a method with a user-controlled name may dispatch to + * an unexpected target, which could cause an exception. + * @kind path-problem + * @problem.severity warning + * @precision high + * @id js/unvalidated-dynamic-method-call + * @tags security + * external/cwe/cwe-754 + */ + +import javascript +import semmle.javascript.security.dataflow.UnvalidatedDynamicMethodCall::UnvalidatedDynamicMethodCall +import DataFlow::PathGraph + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, + "Invocation of method with $@ name may dispatch to unexpected target and cause an exception.", + source.getNode(), "user-controlled" diff --git a/javascript/ql/src/Security/CWE-754/examples/UnvalidatedDynamicMethodCall.js b/javascript/ql/src/Security/CWE-754/examples/UnvalidatedDynamicMethodCall.js new file mode 100644 index 000000000000..6db531150eef --- /dev/null +++ b/javascript/ql/src/Security/CWE-754/examples/UnvalidatedDynamicMethodCall.js @@ -0,0 +1,17 @@ +var express = require('express'); +var app = express(); + +var actions = { + play(data) { + // ... + }, + pause(data) { + // ... + } +} + +app.get('/perform/:action/:payload', function(req, res) { + let action = actions[req.params.action]; + // BAD: `action` may not be a function + res.end(action(req.params.payload)); +}); diff --git a/javascript/ql/src/Security/CWE-754/examples/UnvalidatedDynamicMethodCallGood.js b/javascript/ql/src/Security/CWE-754/examples/UnvalidatedDynamicMethodCallGood.js new file mode 100644 index 000000000000..6c3d5b900e9b --- /dev/null +++ b/javascript/ql/src/Security/CWE-754/examples/UnvalidatedDynamicMethodCallGood.js @@ -0,0 +1,20 @@ +var express = require('express'); +var app = express(); + +var actions = new Map(); +actions.put("play", function play(data) { + // ... +}); +actions.put("pause", function pause(data) { + // ... +}); + +app.get('/perform/:action/:payload', function(req, res) { + if (actions.has(req.params.action)) { + let action = actions.get(req.params.action); + // GOOD: `action` is either the `play` or the `pause` function from above + res.end(action(req.params.payload)); + } else { + res.end("Unsupported action."); + } +}); diff --git a/javascript/ql/src/Security/CWE-754/examples/UnvalidatedDynamicMethodCallGood2.js b/javascript/ql/src/Security/CWE-754/examples/UnvalidatedDynamicMethodCallGood2.js new file mode 100644 index 000000000000..8e627b87cd0e --- /dev/null +++ b/javascript/ql/src/Security/CWE-754/examples/UnvalidatedDynamicMethodCallGood2.js @@ -0,0 +1,23 @@ +var express = require('express'); +var app = express(); + +var actions = { + play(data) { + // ... + }, + pause(data) { + // ... + } +} + +app.get('/perform/:action/:payload', function(req, res) { + if (actions.hasOwnProperty(req.params.action)) { + let action = actions[req.params.action]; + if (typeof action === 'function') { + // GOOD: `action` is an own method of `actions` + res.end(action(req.params.payload)); + return; + } + } + res.end("Unsupported action."); +}); diff --git a/javascript/ql/src/plugin.xml b/javascript/ql/src/plugin.xml deleted file mode 100644 index 19bcfc22249f..000000000000 --- a/javascript/ql/src/plugin.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/javascript/ql/src/semmle/javascript/Expr.qll b/javascript/ql/src/semmle/javascript/Expr.qll index a5ebb5d6b607..27fda3fbbd40 100644 --- a/javascript/ql/src/semmle/javascript/Expr.qll +++ b/javascript/ql/src/semmle/javascript/Expr.qll @@ -1901,4 +1901,36 @@ private class LiteralDynamicImportPath extends PathExprInModule, ConstantString } override string getValue() { result = this.(ConstantString).getStringValue() } -} \ No newline at end of file +} + +/** + * A call or member access that evaluates to `undefined` if its base operand evaluates to `undefined` or `null`. + */ +class OptionalUse extends Expr, @optionalchainable { OptionalUse() { isOptionalChaining(this) } } + +private class ChainElem extends Expr, @optionalchainable { + /** + * Gets the base operand of this chainable element. + */ + ChainElem getChainBase() { + result = this.(CallExpr).getCallee() or + result = this.(PropAccess).getBase() + } +} + +/** + * The root in a chain of calls or property accesses, where at least one call or property access is optional. + */ +class OptionalChainRoot extends ChainElem { + OptionalUse optionalUse; + + OptionalChainRoot() { + getChainBase*() = optionalUse and + not exists(ChainElem other | this = other.getChainBase()) + } + + /** + * Gets an optional call or property access in the chain of this root. + */ + OptionalUse getAnOptionalUse() { result = optionalUse } +} diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/BasicExprTypeInference.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/BasicExprTypeInference.qll index 17fc893239f2..57e0ac44f977 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/BasicExprTypeInference.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/BasicExprTypeInference.qll @@ -413,3 +413,15 @@ private class AnalyzedAssignAddExpr extends AnalyzedCompoundAssignExpr { isAddition(astNode) and result = abstractValueOfType(TTNumber()) } } + +/** + * Flow analysis for optional chaining expressions. + */ +private class AnalyzedOptionalChainExpr extends DataFlow::AnalyzedValueNode { + override OptionalChainRoot astNode; + + override AbstractValue getALocalValue() { + result = super.getALocalValue() or + result = TAbstractUndefined() + } +} diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll index 68568f8a306f..15f4cddc5ad4 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll @@ -157,6 +157,10 @@ abstract class CallWithNonLocalAnalyzedReturnFlow extends DataFlow::AnalyzedValu override AbstractValue getAValue() { result = getACallee().getAReturnValue() + or + // special case from the local layer (could be more precise if it is inferred that the callee is not `null`/`undefined`) + astNode instanceof OptionalChainRoot and + result = TAbstractUndefined() } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/PropertyInjectionShared.qll b/javascript/ql/src/semmle/javascript/security/dataflow/PropertyInjectionShared.qll new file mode 100644 index 000000000000..633ce99aaed5 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/dataflow/PropertyInjectionShared.qll @@ -0,0 +1,47 @@ +/** + * Provides predicates for reasoning about flow of user-controlled values that are used + * as property names. + */ +import javascript + +module PropertyInjection { + /** + * A data-flow node that sanitizes user-controlled property names that flow through it. + */ + abstract class Sanitizer extends DataFlow::Node { + } + + /** + * Concatenation with a constant, acting as a sanitizer. + */ + private class ConcatSanitizer extends Sanitizer { + ConcatSanitizer() { + StringConcatenation::getAnOperand(this).asExpr() instanceof ConstantString + } + } + + /** + * Holds if the methods of the given value are unsafe, such as `eval`. + */ + predicate hasUnsafeMethods(DataFlow::SourceNode node) { + // eval and friends can be accessed from the global object. + node = DataFlow::globalObjectRef() + or + // document.write can be accessed + isDocument(node.asExpr()) + or + // 'constructor' property leads to the Function constructor. + node.analyze().getAValue() instanceof AbstractCallable + or + // Assume that a value that is invoked can refer to a function. + exists (node.getAnInvocation()) + } + + /** + * Holds if the `node` is of form `Object.create(null)` and so it has no prototype. + */ + predicate isPrototypeLessObject(DataFlow::MethodCallNode node) { + node = DataFlow::globalVarRef("Object").getAMethodCall("create") and + node.getArgument(0).asExpr() instanceof NullLiteral + } +} diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/RemotePropertyInjection.qll b/javascript/ql/src/semmle/javascript/security/dataflow/RemotePropertyInjection.qll index 1b2b20ad8e1e..6d4cd47c2bfd 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/RemotePropertyInjection.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/RemotePropertyInjection.qll @@ -1,11 +1,12 @@ /** - * Provides a taint tracking configuration for reasoning about injections in - * property names, used either for writing into a property, into a header or + * Provides a taint tracking configuration for reasoning about injections in + * property names, used either for writing into a property, into a header or * for calling an object's method. */ import javascript import semmle.javascript.frameworks.Express +import PropertyInjectionShared module RemotePropertyInjection { /** @@ -17,11 +18,11 @@ module RemotePropertyInjection { * A data flow sink for remote property injection. */ abstract class Sink extends DataFlow::Node { - + /** * Gets a string to identify the different types of sinks. */ - abstract string getMessage(); + abstract string getMessage(); } /** @@ -45,78 +46,50 @@ module RemotePropertyInjection { override predicate isSanitizer(DataFlow::Node node) { super.isSanitizer(node) or - node instanceof Sanitizer + node instanceof Sanitizer or + node instanceof PropertyInjection::Sanitizer } } /** - * A source of remote user input, considered as a flow source for remote property - * injection. + * A source of remote user input, considered as a flow source for remote property + * injection. */ class RemoteFlowSourceAsSource extends Source { RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource } } /** - * A sink for property writes with dynamically computed property name. + * A sink for property writes with dynamically computed property name. */ class PropertyWriteSink extends Sink, DataFlow::ValueNode { PropertyWriteSink() { - exists (DataFlow::PropWrite pw | astNode = pw.getPropertyNameExpr()) or - exists (DeleteExpr expr | expr.getOperand().(PropAccess).getPropertyNameExpr() = astNode) + exists (DataFlow::PropWrite pw | astNode = pw.getPropertyNameExpr()) or + exists (DeleteExpr expr | expr.getOperand().(PropAccess).getPropertyNameExpr() = astNode) } override string getMessage() { result = " a property name to write to." - } - } - - /** - * A sink for method calls using dynamically computed method names. - */ - class MethodCallSink extends Sink, DataFlow::ValueNode { - MethodCallSink() { - exists (DataFlow::PropRead pr | astNode = pr.getPropertyNameExpr() | - exists (pr.getAnInvocation()) - ) - } - - override string getMessage() { - result = " a method name to be called." } } - - /** - * A sink for HTTP header writes with dynamically computed header name. - * This sink avoids double-flagging by ignoring `SetMultipleHeaders` since - * the multiple headers use case consists of an objects containing different - * header names as properties. This case is already handled by - * `PropertyWriteSink`. + + /** + * A sink for HTTP header writes with dynamically computed header name. + * This sink avoids double-flagging by ignoring `SetMultipleHeaders` since + * the multiple headers use case consists of an objects containing different + * header names as properties. This case is already handled by + * `PropertyWriteSink`. */ class HeaderNameSink extends Sink, DataFlow::ValueNode { HeaderNameSink() { - exists (HTTP::ExplicitHeaderDefinition hd | - not hd instanceof Express::SetMultipleHeaders and - astNode = hd.getNameExpr() - ) + exists (HTTP::ExplicitHeaderDefinition hd | + not hd instanceof Express::SetMultipleHeaders and + astNode = hd.getNameExpr() + ) } override string getMessage() { result = " a header name." } } - - /** - * A binary expression that sanitzes a value for remote property injection. That - * is, if a string is prepended or appended to the remote input, an attacker - * cannot access arbitrary properties. - */ - class ConcatSanitizer extends Sanitizer, DataFlow::ValueNode { - - override BinaryExpr astNode; - - ConcatSanitizer() { - astNode.getAnOperand() instanceof ConstantString - } - } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeDynamicMethodAccess.qll b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeDynamicMethodAccess.qll new file mode 100644 index 000000000000..dace67b1bb9a --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeDynamicMethodAccess.qll @@ -0,0 +1,121 @@ +/** + * Provides a taint-tracking configuration for reasoning about method invocations + * with a user-controlled method name on objects with unsafe methods. + */ + +import javascript +import semmle.javascript.frameworks.Express +import PropertyInjectionShared + +module UnsafeDynamicMethodAccess { + private import DataFlow::FlowLabel + + /** + * A data flow source for unsafe dynamic method access. + */ + abstract class Source extends DataFlow::Node { + /** + * Gets the flow label relevant for this source. + */ + DataFlow::FlowLabel getFlowLabel() { + result = data() + } + } + + /** + * A data flow sink for unsafe dynamic method access. + */ + abstract class Sink extends DataFlow::Node { + /** + * Gets the flow label relevant for this sink + */ + abstract DataFlow::FlowLabel getFlowLabel(); + } + + /** + * A sanitizer for unsafe dynamic method access. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * Gets the flow label describing values that may refer to an unsafe + * function as a result of an attacker-controlled property name. + */ + UnsafeFunction unsafeFunction() { any() } + private class UnsafeFunction extends DataFlow::FlowLabel { + UnsafeFunction() { this = "UnsafeFunction" } + } + + /** + * A taint-tracking configuration for reasoning about unsafe dynamic method access. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "UnsafeDynamicMethodAccess" } + + override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) { + source.(Source).getFlowLabel() = label + } + + override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) { + sink.(Sink).getFlowLabel() = label + } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer or + node instanceof PropertyInjection::Sanitizer + } + + /** + * Holds if a property of the given object is an unsafe function. + */ + predicate hasUnsafeMethods(DataFlow::SourceNode node) { + PropertyInjection::hasUnsafeMethods(node) // Redefined here so custom queries can override it + } + + override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel, DataFlow::FlowLabel dstlabel) { + // Reading a property of the global object or of a function + exists (DataFlow::PropRead read | + hasUnsafeMethods(read.getBase().getALocalSource()) and + src = read.getPropertyNameExpr().flow() and + dst = read and + (srclabel = data() or srclabel = taint()) and + dstlabel = unsafeFunction()) + or + // Reading a chain of properties from any object with a prototype can lead to Function + exists (PropertyProjection proj | + not PropertyInjection::isPrototypeLessObject(proj.getObject().getALocalSource()) and + src = proj.getASelector() and + dst = proj and + (srclabel = data() or srclabel = taint()) and + dstlabel = unsafeFunction()) + } + } + + /** + * A source of remote user input, considered as a source for unsafe dynamic method access. + */ + class RemoteFlowSourceAsSource extends Source { + RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource } + } + + /** + * The page URL considered as a flow source for unsafe dynamic method access. + */ + class DocumentUrlAsSource extends Source { + DocumentUrlAsSource() { isDocumentURL(asExpr()) } + } + + /** + * A function invocation of an unsafe function, as a sink for remote unsafe dynamic method access. + */ + class CalleeAsSink extends Sink { + CalleeAsSink() { + this = any(DataFlow::InvokeNode node).getCalleeNode() + } + + override DataFlow::FlowLabel getFlowLabel() { + result = unsafeFunction() + } + } +} diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCall.qll b/javascript/ql/src/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCall.qll new file mode 100644 index 000000000000..f916c67a3e93 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCall.qll @@ -0,0 +1,153 @@ +/** + * Provides a taint-tracking configuration for reasoning about unvalidated dynamic + * method calls. + */ + +import javascript +import semmle.javascript.frameworks.Express +import PropertyInjectionShared +private import semmle.javascript.dataflow.InferredTypes + +module UnvalidatedDynamicMethodCall { + private import DataFlow::FlowLabel + + /** + * A data flow source for unvalidated dynamic method calls. + */ + abstract class Source extends DataFlow::Node { + /** + * Gets the flow label relevant for this source. + */ + DataFlow::FlowLabel getFlowLabel() { + result = data() + } + } + + /** + * A data flow sink for unvalidated dynamic method calls. + */ + abstract class Sink extends DataFlow::Node { + /** + * Gets the flow label relevant for this sink + */ + abstract DataFlow::FlowLabel getFlowLabel(); + } + + /** + * A sanitizer for unvalidated dynamic method calls. + */ + abstract class Sanitizer extends DataFlow::Node { + abstract predicate sanitizes(DataFlow::Node source, DataFlow::Node sink, DataFlow::FlowLabel lbl); + } + + /** + * A flow label describing values read from a user-controlled property that + * may not be functions. + */ + private class MaybeNonFunction extends DataFlow::FlowLabel { + MaybeNonFunction() { this = "MaybeNonFunction" } + } + + /** + * A flow label describing values read from a user-controlled property that + * may originate from a prototype object. + */ + private class MaybeFromProto extends DataFlow::FlowLabel { + MaybeFromProto() { this = "MaybeFromProto" } + } + + /** + * A taint-tracking configuration for reasoning about unvalidated dynamic method calls. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "UnvalidatedDynamicMethodCall" } + + override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) { + source.(Source).getFlowLabel() = label + } + + override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) { + sink.(Sink).getFlowLabel() = label + } + + override predicate isSanitizer(DataFlow::Node nd) { + super.isSanitizer(nd) or + nd instanceof PropertyInjection::Sanitizer + } + + override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel, DataFlow::FlowLabel dstlabel) { + exists (DataFlow::PropRead read | + src = read.getPropertyNameExpr().flow() and + dst = read and + (srclabel = data() or srclabel = taint()) and + (dstlabel instanceof MaybeNonFunction + or + // a property of `Object.create(null)` cannot come from a prototype + not PropertyInjection::isPrototypeLessObject(read.getBase().getALocalSource()) and + dstlabel instanceof MaybeFromProto) and + // avoid overlapping results with unsafe dynamic method access query + not PropertyInjection::hasUnsafeMethods(read.getBase().getALocalSource()) + ) + } + } + + /** + * A source of remote user input, considered as a source for unvalidated dynamic method calls. + */ + class RemoteFlowSourceAsSource extends Source { + RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource } + } + + /** + * The page URL considered as a flow source for unvalidated dynamic method calls. + */ + class DocumentUrlAsSource extends Source { + DocumentUrlAsSource() { isDocumentURL(asExpr()) } + } + + /** + * A function invocation of an unsafe function, as a sink for remote unvalidated dynamic method calls. + */ + class CalleeAsSink extends Sink { + InvokeExpr invk; + + CalleeAsSink() { + this = invk.getCallee().flow() and + // don't flag invocations inside a try-catch + not invk.getASuccessor() instanceof CatchClause + } + + override DataFlow::FlowLabel getFlowLabel() { + result instanceof MaybeNonFunction and + // don't flag if the type inference can prove that it is a function; + // this complements the `FunctionCheck` sanitizer below: the type inference can + // detect more checks locally, but doesn't provide inter-procedural reasoning + this.analyze().getAType() != TTFunction() + or + result instanceof MaybeFromProto + } + } + + /** + * A check of the form `typeof x === 'function'`, which sanitizes away the `MaybeNonFunction` + * taint kind. + */ + class FunctionCheck extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::ValueNode { + override EqualityTest astNode; + TypeofExpr t; + + FunctionCheck() { + astNode.getAnOperand().getStringValue() = "function" and + astNode.getAnOperand().getUnderlyingValue() = t + } + + override predicate sanitizes(boolean outcome, Expr e) { + outcome = astNode.getPolarity() and + e = t.getOperand().getUnderlyingValue() + } + + override DataFlow::FlowLabel getALabel() { + result instanceof MaybeNonFunction + } + } +} diff --git a/javascript/ql/src/semmlecode.javascript.dbscheme b/javascript/ql/src/semmlecode.javascript.dbscheme index 6486c78671c4..81e6619c681f 100644 --- a/javascript/ql/src/semmlecode.javascript.dbscheme +++ b/javascript/ql/src/semmlecode.javascript.dbscheme @@ -1077,4 +1077,8 @@ xmllocations( @dataflownode = @expr | @functiondeclstmt | @classdeclstmt | @namespacedeclaration | @enumdeclaration | @property; -/* Last updated 2017/07/11. */ +@optionalchainable = @callexpr | @propaccess; + +isOptionalChaining(int id: @optionalchainable ref); + +/* Last updated 2018/10/22. */ diff --git a/javascript/ql/src/semmlecode.javascript.dbscheme.stats b/javascript/ql/src/semmlecode.javascript.dbscheme.stats index 9ec01562f78b..17ef2dd9d8e8 100644 --- a/javascript/ql/src/semmlecode.javascript.dbscheme.stats +++ b/javascript/ql/src/semmlecode.javascript.dbscheme.stats @@ -1393,6 +1393,10 @@ @xmlcharacters 439958 + +@optionalchainable +100 + @@ -19773,6 +19777,18 @@ + +isOptionalChaining +100 + + +id +100 + + + + + rangeQuantifierLowerBound 146 diff --git a/javascript/ql/test/library-tests/OptionalChaining/OptionalChainRoot.expected b/javascript/ql/test/library-tests/OptionalChaining/OptionalChainRoot.expected new file mode 100644 index 000000000000..5ec86ee74624 --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/OptionalChainRoot.expected @@ -0,0 +1,18 @@ +| short-circuiting.js:3:5:3:18 | x?.(o1 = null) | short-circuiting.js:3:5:3:18 | x?.(o1 = null) | +| short-circuiting.js:7:5:7:18 | x?.[o2 = null] | short-circuiting.js:7:5:7:18 | x?.[o2 = null] | +| short-circuiting.js:12:5:12:31 | x?.[o3 ... = null) | short-circuiting.js:12:5:12:18 | x?.[o3 = null] | +| short-circuiting.js:12:5:12:31 | x?.[o3 ... = null) | short-circuiting.js:12:5:12:31 | x?.[o3 ... = null) | +| tst.js:2:1:2:6 | a?.b.c | tst.js:2:1:2:4 | a?.b | +| tst.js:3:1:3:6 | a.b?.c | tst.js:3:1:3:6 | a.b?.c | +| tst.js:4:1:4:7 | a?.b?.c | tst.js:4:1:4:4 | a?.b | +| tst.js:4:1:4:7 | a?.b?.c | tst.js:4:1:4:7 | a?.b?.c | +| tst.js:7:1:7:7 | f?.()() | tst.js:7:1:7:5 | f?.() | +| tst.js:8:1:8:7 | f()?.() | tst.js:8:1:8:7 | f()?.() | +| tst.js:9:1:9:9 | f?.()?.() | tst.js:9:1:9:5 | f?.() | +| tst.js:9:1:9:9 | f?.()?.() | tst.js:9:1:9:9 | f?.()?.() | +| tst.js:12:1:12:8 | a?.m().b | tst.js:12:1:12:4 | a?.m | +| tst.js:13:1:13:9 | a.m?.().b | tst.js:13:1:13:7 | a.m?.() | +| tst.js:14:1:14:8 | a.m()?.b | tst.js:14:1:14:8 | a.m()?.b | +| tst.js:15:1:15:11 | a?.m?.()?.b | tst.js:15:1:15:4 | a?.m | +| tst.js:15:1:15:11 | a?.m?.()?.b | tst.js:15:1:15:8 | a?.m?.() | +| tst.js:15:1:15:11 | a?.m?.()?.b | tst.js:15:1:15:11 | a?.m?.()?.b | diff --git a/javascript/ql/test/library-tests/OptionalChaining/OptionalChainRoot.ql b/javascript/ql/test/library-tests/OptionalChaining/OptionalChainRoot.ql new file mode 100644 index 000000000000..f11b7fe54ab7 --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/OptionalChainRoot.ql @@ -0,0 +1,4 @@ +import javascript + +from OptionalChainRoot root +select root, root.getAnOptionalUse() diff --git a/javascript/ql/test/library-tests/OptionalChaining/OptionalUse.expected b/javascript/ql/test/library-tests/OptionalChaining/OptionalUse.expected new file mode 100644 index 000000000000..430d4d522998 --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/OptionalUse.expected @@ -0,0 +1,18 @@ +| short-circuiting.js:3:5:3:18 | x?.(o1 = null) | +| short-circuiting.js:7:5:7:18 | x?.[o2 = null] | +| short-circuiting.js:12:5:12:18 | x?.[o3 = null] | +| short-circuiting.js:12:5:12:31 | x?.[o3 ... = null) | +| tst.js:2:1:2:4 | a?.b | +| tst.js:3:1:3:6 | a.b?.c | +| tst.js:4:1:4:4 | a?.b | +| tst.js:4:1:4:7 | a?.b?.c | +| tst.js:7:1:7:5 | f?.() | +| tst.js:8:1:8:7 | f()?.() | +| tst.js:9:1:9:5 | f?.() | +| tst.js:9:1:9:9 | f?.()?.() | +| tst.js:12:1:12:4 | a?.m | +| tst.js:13:1:13:7 | a.m?.() | +| tst.js:14:1:14:8 | a.m()?.b | +| tst.js:15:1:15:4 | a?.m | +| tst.js:15:1:15:8 | a?.m?.() | +| tst.js:15:1:15:11 | a?.m?.()?.b | diff --git a/javascript/ql/test/library-tests/OptionalChaining/OptionalUse.ql b/javascript/ql/test/library-tests/OptionalChaining/OptionalUse.ql new file mode 100644 index 000000000000..c53cf68d7b9c --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/OptionalUse.ql @@ -0,0 +1,3 @@ +import javascript + +select any(OptionalUse u) \ No newline at end of file diff --git a/javascript/ql/test/library-tests/OptionalChaining/ShortCircuiting.expected b/javascript/ql/test/library-tests/OptionalChaining/ShortCircuiting.expected new file mode 100644 index 000000000000..d61dccc8c5a3 --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/ShortCircuiting.expected @@ -0,0 +1,8 @@ +| short-circuiting.js:4:10:4:11 | o1 | file://:0:0:0:0 | null | +| short-circuiting.js:4:10:4:11 | o1 | short-circuiting.js:2:14:2:15 | object literal | +| short-circuiting.js:8:10:8:11 | o2 | file://:0:0:0:0 | null | +| short-circuiting.js:8:10:8:11 | o2 | short-circuiting.js:6:14:6:15 | object literal | +| short-circuiting.js:13:10:13:11 | o3 | file://:0:0:0:0 | null | +| short-circuiting.js:13:10:13:11 | o3 | short-circuiting.js:10:14:10:15 | object literal | +| short-circuiting.js:14:10:14:11 | o4 | file://:0:0:0:0 | null | +| short-circuiting.js:14:10:14:11 | o4 | short-circuiting.js:11:14:11:15 | object literal | diff --git a/javascript/ql/test/library-tests/OptionalChaining/ShortCircuiting.ql b/javascript/ql/test/library-tests/OptionalChaining/ShortCircuiting.ql new file mode 100644 index 000000000000..5f51047ea72f --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/ShortCircuiting.ql @@ -0,0 +1,7 @@ +import javascript + +from CallExpr c, Expr arg +where + c.getCalleeName() = "DUMP" and + arg = c.getArgument(0) +select arg, arg.analyze().getAValue() diff --git a/javascript/ql/test/library-tests/OptionalChaining/ShortCirtuiting.expected b/javascript/ql/test/library-tests/OptionalChaining/ShortCirtuiting.expected new file mode 100644 index 000000000000..d61dccc8c5a3 --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/ShortCirtuiting.expected @@ -0,0 +1,8 @@ +| short-circuiting.js:4:10:4:11 | o1 | file://:0:0:0:0 | null | +| short-circuiting.js:4:10:4:11 | o1 | short-circuiting.js:2:14:2:15 | object literal | +| short-circuiting.js:8:10:8:11 | o2 | file://:0:0:0:0 | null | +| short-circuiting.js:8:10:8:11 | o2 | short-circuiting.js:6:14:6:15 | object literal | +| short-circuiting.js:13:10:13:11 | o3 | file://:0:0:0:0 | null | +| short-circuiting.js:13:10:13:11 | o3 | short-circuiting.js:10:14:10:15 | object literal | +| short-circuiting.js:14:10:14:11 | o4 | file://:0:0:0:0 | null | +| short-circuiting.js:14:10:14:11 | o4 | short-circuiting.js:11:14:11:15 | object literal | diff --git a/javascript/ql/test/library-tests/OptionalChaining/ShortCirtuiting.ql b/javascript/ql/test/library-tests/OptionalChaining/ShortCirtuiting.ql new file mode 100644 index 000000000000..5f51047ea72f --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/ShortCirtuiting.ql @@ -0,0 +1,7 @@ +import javascript + +from CallExpr c, Expr arg +where + c.getCalleeName() = "DUMP" and + arg = c.getArgument(0) +select arg, arg.analyze().getAValue() diff --git a/javascript/ql/test/library-tests/OptionalChaining/short-circuiting.js b/javascript/ql/test/library-tests/OptionalChaining/short-circuiting.js new file mode 100644 index 000000000000..97576676994f --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/short-circuiting.js @@ -0,0 +1,16 @@ +(function() { + var o1 = {}; + x?.(o1 = null); + DUMP(o1); + + var o2 = {}; + x?.[o2 = null]; + DUMP(o2); + + var o3 = {}, + o4 = {}; + x?.[o3 = null]?.(o4 = null); + DUMP(o3); + DUMP(o4); +}); +// semmle-extractor-options: --experimental diff --git a/javascript/ql/test/library-tests/OptionalChaining/tst.js b/javascript/ql/test/library-tests/OptionalChaining/tst.js new file mode 100644 index 000000000000..b0ee74357220 --- /dev/null +++ b/javascript/ql/test/library-tests/OptionalChaining/tst.js @@ -0,0 +1,17 @@ +a.b.c; +a?.b.c; +a.b?.c; +a?.b?.c; + +f()(); +f?.()(); +f()?.(); +f?.()?.(); + +a.m().b; +a?.m().b; +a.m?.().b; +a.m()?.b; +a?.m?.()?.b; + +// semmle-extractor-options: --experimental diff --git a/javascript/ql/test/library-tests/TypeInference/OptionalChaining/test.expected b/javascript/ql/test/library-tests/TypeInference/OptionalChaining/test.expected new file mode 100644 index 000000000000..450db73b17b6 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeInference/OptionalChaining/test.expected @@ -0,0 +1,22 @@ +| tst.js:2:14:2:21 | (null)() | file://:0:0:0:0 | indefinite value (call) | +| tst.js:3:14:3:23 | (null)?.() | file://:0:0:0:0 | indefinite value (call) | +| tst.js:3:14:3:23 | (null)?.() | file://:0:0:0:0 | undefined | +| tst.js:4:14:4:26 | (undefined)() | file://:0:0:0:0 | indefinite value (call) | +| tst.js:5:14:5:28 | (undefined)?.() | file://:0:0:0:0 | indefinite value (call) | +| tst.js:5:14:5:28 | (undefined)?.() | file://:0:0:0:0 | undefined | +| tst.js:6:14:6:24 | (unknown)() | file://:0:0:0:0 | indefinite value (call) | +| tst.js:7:14:7:26 | (unknown)?.() | file://:0:0:0:0 | indefinite value (call) | +| tst.js:7:14:7:26 | (unknown)?.() | file://:0:0:0:0 | undefined | +| tst.js:9:13:11:5 | unknown ... ;\\n } | file://:0:0:0:0 | undefined | +| tst.js:9:13:11:5 | unknown ... ;\\n } | tst.js:9:33:11:5 | anonymous function | +| tst.js:12:14:12:16 | f() | file://:0:0:0:0 | non-zero value | +| tst.js:13:14:13:18 | f?.() | file://:0:0:0:0 | non-zero value | +| tst.js:13:14:13:18 | f?.() | file://:0:0:0:0 | undefined | +| tst.js:15:13:17:5 | functio ... ;\\n } | tst.js:15:13:17:5 | function g | +| tst.js:18:14:18:16 | g() | file://:0:0:0:0 | non-zero value | +| tst.js:19:15:19:19 | g?.() | file://:0:0:0:0 | non-zero value | +| tst.js:19:15:19:19 | g?.() | file://:0:0:0:0 | undefined | +| tst.js:21:13:21:21 | undefined | file://:0:0:0:0 | undefined | +| tst.js:22:15:22:17 | h() | file://:0:0:0:0 | indefinite value (call) | +| tst.js:23:15:23:19 | h?.() | file://:0:0:0:0 | indefinite value (call) | +| tst.js:23:15:23:19 | h?.() | file://:0:0:0:0 | undefined | diff --git a/javascript/ql/test/library-tests/TypeInference/OptionalChaining/test.ql b/javascript/ql/test/library-tests/TypeInference/OptionalChaining/test.ql new file mode 100644 index 000000000000..465b085408de --- /dev/null +++ b/javascript/ql/test/library-tests/TypeInference/OptionalChaining/test.ql @@ -0,0 +1,5 @@ +import javascript + +from Variable v, Expr e +where e = v.getAnAssignedExpr() +select e, e.analyze().getAValue() \ No newline at end of file diff --git a/javascript/ql/test/library-tests/TypeInference/OptionalChaining/tst.js b/javascript/ql/test/library-tests/TypeInference/OptionalChaining/tst.js new file mode 100644 index 000000000000..4890c8335ccc --- /dev/null +++ b/javascript/ql/test/library-tests/TypeInference/OptionalChaining/tst.js @@ -0,0 +1,25 @@ +(function() { + var v1 = (null)(); + var v2 = (null)?.(); + var v3 = (undefined)(); + var v4 = (undefined)?.(); + var v5 = (unknown)(); + var v6 = (unknown)?.(); + + var f = unknown? undefined: function(){ + return 42; + }; + var v7 = f(); + var v8 = f?.(); + + var g = function(){ + return 42; + }; + var v9 = g(); + var v10 = g?.(); + + var h = undefined; + var v11 = h(); + var v12 = h?.(); +}); +// semmle-extractor-options: --experimental diff --git a/javascript/ql/test/query-tests/Expressions/SuspiciousInvocation/SuspiciousInvocation.expected b/javascript/ql/test/query-tests/Expressions/SuspiciousInvocation/SuspiciousInvocation.expected index 1bd5a7ecf895..d753f3b6bc3e 100644 --- a/javascript/ql/test/query-tests/Expressions/SuspiciousInvocation/SuspiciousInvocation.expected +++ b/javascript/ql/test/query-tests/Expressions/SuspiciousInvocation/SuspiciousInvocation.expected @@ -1,3 +1,5 @@ | SuspiciousInvocation.js:11:5:11:58 | error(" ... status) | Callee is not a function: it has type undefined. | | namespace.ts:23:1:23:3 | g() | Callee is not a function: it has type object. | +| optional-chaining.js:3:5:3:7 | a() | Callee is not a function: it has type null. | +| optional-chaining.js:7:5:7:7 | b() | Callee is not a function: it has type undefined. | | super.js:11:5:11:11 | super() | Callee is not a function: it has type number. | diff --git a/javascript/ql/test/query-tests/Expressions/SuspiciousInvocation/optional-chaining.js b/javascript/ql/test/query-tests/Expressions/SuspiciousInvocation/optional-chaining.js new file mode 100644 index 000000000000..2df3535216ab --- /dev/null +++ b/javascript/ql/test/query-tests/Expressions/SuspiciousInvocation/optional-chaining.js @@ -0,0 +1,10 @@ +(function(){ + var a = null; + a(); + a?.(); + + var b = undefined; + b(); + b?.(); +}); +// semmle-extractor-options: --experimental diff --git a/javascript/ql/test/query-tests/Expressions/SuspiciousPropAccess/SuspiciousPropAccess.expected b/javascript/ql/test/query-tests/Expressions/SuspiciousPropAccess/SuspiciousPropAccess.expected index 98f8b71952d3..672ea8389831 100644 --- a/javascript/ql/test/query-tests/Expressions/SuspiciousPropAccess/SuspiciousPropAccess.expected +++ b/javascript/ql/test/query-tests/Expressions/SuspiciousPropAccess/SuspiciousPropAccess.expected @@ -1,4 +1,6 @@ | SuspiciousPropAccess.js:4:10:4:21 | result.value | The base expression of this property access is always undefined. | +| optional-chaining.js:3:5:3:7 | a.p | The base expression of this property access is always null. | +| optional-chaining.js:7:5:7:7 | b.p | The base expression of this property access is always undefined. | | tst.js:32:32:32:38 | a(1)[0] | The base expression of this property access is always null. | | tst.ts:19:3:19:5 | x.p | The base expression of this property access is always undefined. | | typeassertion.ts:14:3:14:9 | z.field | The base expression of this property access is always null. | diff --git a/javascript/ql/test/query-tests/Expressions/SuspiciousPropAccess/optional-chaining.js b/javascript/ql/test/query-tests/Expressions/SuspiciousPropAccess/optional-chaining.js new file mode 100644 index 000000000000..9f6625072271 --- /dev/null +++ b/javascript/ql/test/query-tests/Expressions/SuspiciousPropAccess/optional-chaining.js @@ -0,0 +1,10 @@ +(function(){ + var a = null; + a.p; + a?.p; + + var b = undefined; + b.p; + b?.p; +}); +// semmle-extractor-options: --experimental diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer_old.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer_old.expected index bfbe2040225b..621e6dcbf8e5 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer_old.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer_old.expected @@ -1,7 +1,7 @@ +WARNING: Predicate flowsFrom has been deprecated and may be removed in future (ReflectedXssWithCustomSanitizer_old.ql:21,11-20) WARNING: Type SanitizingGuard has been deprecated and may be removed in future (ReflectedXssWithCustomSanitizer_old.ql:8,34-64) WARNING: Type XssDataFlowConfiguration has been deprecated and may be removed in future (ReflectedXssWithCustomSanitizer_old.ql:14,20-44) WARNING: Type XssDataFlowConfiguration has been deprecated and may be removed in future (ReflectedXssWithCustomSanitizer_old.ql:20,6-30) -WARNING: Predicate flowsFrom has been deprecated and may be removed in future (ReflectedXssWithCustomSanitizer_old.ql:21,11-20) | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value | | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | | formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected similarity index 100% rename from javascript/ql/test/query-tests/Security/CWE-094/CodeInjection.expected rename to javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection.qlref b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.qlref similarity index 100% rename from javascript/ql/test/query-tests/Security/CWE-094/CodeInjection.qlref rename to javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.qlref diff --git a/javascript/ql/test/query-tests/Security/CWE-094/angularjs.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/angularjs.js similarity index 100% rename from javascript/ql/test/query-tests/Security/CWE-094/angularjs.js rename to javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/angularjs.js diff --git a/javascript/ql/test/query-tests/Security/CWE-094/eslint-escope-build.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/eslint-escope-build.js similarity index 100% rename from javascript/ql/test/query-tests/Security/CWE-094/eslint-escope-build.js rename to javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/eslint-escope-build.js diff --git a/javascript/ql/test/query-tests/Security/CWE-094/express.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/express.js similarity index 100% rename from javascript/ql/test/query-tests/Security/CWE-094/express.js rename to javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/express.js diff --git a/javascript/ql/test/query-tests/Security/CWE-094/externs.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/externs.js similarity index 100% rename from javascript/ql/test/query-tests/Security/CWE-094/externs.js rename to javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/externs.js diff --git a/javascript/ql/test/query-tests/Security/CWE-094/react-native.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/react-native.js similarity index 100% rename from javascript/ql/test/query-tests/Security/CWE-094/react-native.js rename to javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/react-native.js diff --git a/javascript/ql/test/query-tests/Security/CWE-094/tst.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/tst.js similarity index 100% rename from javascript/ql/test/query-tests/Security/CWE-094/tst.js rename to javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/tst.js diff --git a/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/UnsafeDynamicMethodAccess.expected b/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/UnsafeDynamicMethodAccess.expected new file mode 100644 index 000000000000..e9de22bffb86 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/UnsafeDynamicMethodAccess.expected @@ -0,0 +1,54 @@ +nodes +| example.js:9:37:9:38 | ev | +| example.js:10:9:10:37 | message | +| example.js:10:19:10:37 | JSON.parse(ev.data) | +| example.js:10:30:10:31 | ev | +| example.js:10:30:10:36 | ev.data | +| example.js:13:5:13:24 | window[message.name] | +| example.js:13:12:13:18 | message | +| example.js:13:12:13:23 | message.name | +| tst.js:3:37:3:38 | ev | +| tst.js:4:9:4:37 | message | +| tst.js:4:19:4:37 | JSON.parse(ev.data) | +| tst.js:4:30:4:31 | ev | +| tst.js:4:30:4:36 | ev.data | +| tst.js:5:5:5:24 | window[message.name] | +| tst.js:5:12:5:18 | message | +| tst.js:5:12:5:23 | message.name | +| tst.js:6:9:6:28 | window[message.name] | +| tst.js:6:16:6:22 | message | +| tst.js:6:16:6:27 | message.name | +| tst.js:11:5:11:19 | f[message.name] | +| tst.js:11:7:11:13 | message | +| tst.js:11:7:11:18 | message.name | +| tst.js:15:5:15:14 | window[ev] | +| tst.js:15:12:15:13 | ev | +edges +| example.js:9:37:9:38 | ev | example.js:10:30:10:31 | ev | +| example.js:10:9:10:37 | message | example.js:13:12:13:18 | message | +| example.js:10:19:10:37 | JSON.parse(ev.data) | example.js:10:9:10:37 | message | +| example.js:10:30:10:31 | ev | example.js:10:30:10:36 | ev.data | +| example.js:10:30:10:36 | ev.data | example.js:10:19:10:37 | JSON.parse(ev.data) | +| example.js:13:12:13:18 | message | example.js:13:12:13:23 | message.name | +| example.js:13:12:13:23 | message.name | example.js:13:5:13:24 | window[message.name] | +| tst.js:3:37:3:38 | ev | tst.js:4:30:4:31 | ev | +| tst.js:3:37:3:38 | ev | tst.js:15:12:15:13 | ev | +| tst.js:4:9:4:37 | message | tst.js:5:12:5:18 | message | +| tst.js:4:9:4:37 | message | tst.js:6:16:6:22 | message | +| tst.js:4:9:4:37 | message | tst.js:11:7:11:13 | message | +| tst.js:4:19:4:37 | JSON.parse(ev.data) | tst.js:4:9:4:37 | message | +| tst.js:4:30:4:31 | ev | tst.js:4:30:4:36 | ev.data | +| tst.js:4:30:4:36 | ev.data | tst.js:4:19:4:37 | JSON.parse(ev.data) | +| tst.js:5:12:5:18 | message | tst.js:5:12:5:23 | message.name | +| tst.js:5:12:5:23 | message.name | tst.js:5:5:5:24 | window[message.name] | +| tst.js:6:16:6:22 | message | tst.js:6:16:6:27 | message.name | +| tst.js:6:16:6:27 | message.name | tst.js:6:9:6:28 | window[message.name] | +| tst.js:11:7:11:13 | message | tst.js:11:7:11:18 | message.name | +| tst.js:11:7:11:18 | message.name | tst.js:11:5:11:19 | f[message.name] | +| tst.js:15:12:15:13 | ev | tst.js:15:5:15:14 | window[ev] | +#select +| example.js:13:5:13:24 | window[message.name] | example.js:9:37:9:38 | ev | example.js:13:5:13:24 | window[message.name] | Invocation of method derived from $@ may lead to remote code execution. | example.js:9:37:9:38 | ev | user-controlled value | +| tst.js:5:5:5:24 | window[message.name] | tst.js:3:37:3:38 | ev | tst.js:5:5:5:24 | window[message.name] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value | +| tst.js:6:9:6:28 | window[message.name] | tst.js:3:37:3:38 | ev | tst.js:6:9:6:28 | window[message.name] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value | +| tst.js:11:5:11:19 | f[message.name] | tst.js:3:37:3:38 | ev | tst.js:11:5:11:19 | f[message.name] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value | +| tst.js:15:5:15:14 | window[ev] | tst.js:3:37:3:38 | ev | tst.js:15:5:15:14 | window[ev] | Invocation of method derived from $@ may lead to remote code execution. | tst.js:3:37:3:38 | ev | user-controlled value | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/UnsafeDynamicMethodAccess.qlref b/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/UnsafeDynamicMethodAccess.qlref new file mode 100644 index 000000000000..5c4a993df5a7 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/UnsafeDynamicMethodAccess.qlref @@ -0,0 +1 @@ +Security/CWE-094/UnsafeDynamicMethodAccess.ql diff --git a/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/example.js b/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/example.js new file mode 100644 index 000000000000..8ffd5a8addda --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/example.js @@ -0,0 +1,14 @@ +// API methods +function play(data) { + // ... +} +function pause(data) { + // ... +} + +window.addEventListener("message", (ev) => { + let message = JSON.parse(ev.data); + + // Let the parent frame call the 'play' or 'pause' function + window[message.name](message.payload); // NOT OK +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/tst.js b/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/tst.js new file mode 100644 index 000000000000..21931585eee7 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-094/UnsafeDynamicMethodAccess/tst.js @@ -0,0 +1,16 @@ +let obj = {}; + +window.addEventListener('message', (ev) => { + let message = JSON.parse(ev.data); + window[message.name](message.payload); // NOT OK - may invoke eval + new window[message.name](message.payload); // NOT OK - may invoke jQuery $ function or similar + window["HTMLElement" + message.name](message.payload); // OK - concatenation restricts choice of methods + window[`HTMLElement${message.name}`](message.payload); // OK - concatenation restricts choice of methods + + function f() {} + f[message.name](message.payload)(); // NOT OK - may acccess Function constructor + + obj[message.name](message.payload); // OK - may crash, but no code execution involved + + window[ev](ev); // NOT OK +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-400/RemotePropertyInjection.expected b/javascript/ql/test/query-tests/Security/CWE-400/RemotePropertyInjection.expected index db2cbb1288c8..1c61836da659 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/RemotePropertyInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-400/RemotePropertyInjection.expected @@ -3,7 +3,6 @@ nodes | tst.js:8:13:8:52 | myCoolL ... rolled) | | tst.js:8:28:8:51 | req.que ... trolled | | tst.js:9:8:9:11 | prop | -| tst.js:11:16:11:19 | prop | | tst.js:13:15:13:18 | prop | | tst.js:14:31:14:34 | prop | | tst.js:16:10:16:13 | prop | @@ -12,7 +11,6 @@ nodes | tstNonExpr.js:8:17:8:23 | userVal | edges | tst.js:8:6:8:52 | prop | tst.js:9:8:9:11 | prop | -| tst.js:8:6:8:52 | prop | tst.js:11:16:11:19 | prop | | tst.js:8:6:8:52 | prop | tst.js:13:15:13:18 | prop | | tst.js:8:6:8:52 | prop | tst.js:14:31:14:34 | prop | | tst.js:8:6:8:52 | prop | tst.js:16:10:16:13 | prop | @@ -22,7 +20,6 @@ edges | tstNonExpr.js:5:17:5:23 | req.url | tstNonExpr.js:5:7:5:23 | userVal | #select | tst.js:9:8:9:11 | prop | tst.js:8:28:8:51 | req.que ... trolled | tst.js:9:8:9:11 | prop | A $@ is used as a property name to write to. | tst.js:8:28:8:51 | req.que ... trolled | user-provided value | -| tst.js:11:16:11:19 | prop | tst.js:8:28:8:51 | req.que ... trolled | tst.js:11:16:11:19 | prop | A $@ is used as a method name to be called. | tst.js:8:28:8:51 | req.que ... trolled | user-provided value | | tst.js:13:15:13:18 | prop | tst.js:8:28:8:51 | req.que ... trolled | tst.js:13:15:13:18 | prop | A $@ is used as a property name to write to. | tst.js:8:28:8:51 | req.que ... trolled | user-provided value | | tst.js:14:31:14:34 | prop | tst.js:8:28:8:51 | req.que ... trolled | tst.js:14:31:14:34 | prop | A $@ is used as a property name to write to. | tst.js:8:28:8:51 | req.que ... trolled | user-provided value | | tst.js:16:10:16:13 | prop | tst.js:8:28:8:51 | req.que ... trolled | tst.js:16:10:16:13 | prop | A $@ is used as a property name to write to. | tst.js:8:28:8:51 | req.que ... trolled | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-400/tst.js b/javascript/ql/test/query-tests/Security/CWE-400/tst.js index 644a5318bb68..a0df1d8879ea 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/tst.js +++ b/javascript/ql/test/query-tests/Security/CWE-400/tst.js @@ -5,20 +5,21 @@ var myObj = {} app.get('/user/:id', function(req, res) { myCoolLocalFct(req.query.userControlled); - var prop = myCoolLocalFct(req.query.userControlled); + var prop = myCoolLocalFct(req.query.userControlled); myObj[prop] = 23; // NOT OK myObj.prop = 23; // OK - var x = myObj[prop]; // NOT OK - x(23); - delete myObj[prop]; // NOT OK + var x = myObj[prop]; // NOT OK, but flagged by different query + x(23); + delete myObj[prop]; // NOT OK Object.defineProperty(myObj, prop, {value: 24}); // NOT OK - var headers = {}; + var headers = {}; headers[prop] = 42; // NOT OK - res.set(headers); + res.set(headers); + myCoolLocalFct[req.query.x](); // OK - flagged by method name injection }); function myCoolLocalFct(x) { var result = x; return result.substring(0, result.length); - + } \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-754/UnsafeDynamicMethodAccess.js b/javascript/ql/test/query-tests/Security/CWE-754/UnsafeDynamicMethodAccess.js new file mode 100644 index 000000000000..30a4d80b3131 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-754/UnsafeDynamicMethodAccess.js @@ -0,0 +1,18 @@ +// copied from tests for `UnsafeDynamicMethodAccess.ql` to check that they do not overlap + +let obj = {}; + +window.addEventListener('message', (ev) => { + let message = JSON.parse(ev.data); + window[message.name](message.payload); // NOT OK, but reported by UnsafeDynamicMethodAccess.ql + new window[message.name](message.payload); // NOT OK, but reported by UnsafeDynamicMethodAccess.ql + window["HTMLElement" + message.name](message.payload); // OK - concatenation restricts choice of methods + window[`HTMLElement${message.name}`](message.payload); // OK - concatenation restricts choice of methods + + function f() {} + f[message.name](message.payload)(); // NOT OK, but reported by UnsafeDynamicMethodAccess.ql + + obj[message.name](message.payload); // NOT OK + + window[ev](ev); // NOT OK, but reported by UnsafeDynamicMethodAccess.ql +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCall.expected b/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCall.expected new file mode 100644 index 000000000000..b91757b60821 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCall.expected @@ -0,0 +1,134 @@ +nodes +| UnsafeDynamicMethodAccess.js:5:37:5:38 | ev | +| UnsafeDynamicMethodAccess.js:6:9:6:37 | message | +| UnsafeDynamicMethodAccess.js:6:19:6:37 | JSON.parse(ev.data) | +| UnsafeDynamicMethodAccess.js:6:30:6:31 | ev | +| UnsafeDynamicMethodAccess.js:6:30:6:36 | ev.data | +| UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | +| UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | +| UnsafeDynamicMethodAccess.js:15:9:15:15 | message | +| UnsafeDynamicMethodAccess.js:15:9:15:20 | message.name | +| UnvalidatedDynamicMethodCall.js:14:7:14:41 | action | +| UnvalidatedDynamicMethodCall.js:14:7:14:41 | action | +| UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] | +| UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] | +| UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | +| UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | +| UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | +| tst.js:6:39:6:40 | ev | +| tst.js:7:9:7:39 | name | +| tst.js:7:16:7:34 | JSON.parse(ev.data) | +| tst.js:7:16:7:39 | JSON.pa ... a).name | +| tst.js:7:27:7:28 | ev | +| tst.js:7:27:7:33 | ev.data | +| tst.js:9:5:9:16 | obj[ev.data] | +| tst.js:9:5:9:16 | obj[ev.data] | +| tst.js:9:9:9:10 | ev | +| tst.js:9:9:9:15 | ev.data | +| tst.js:11:5:11:13 | obj[name] | +| tst.js:11:5:11:13 | obj[name] | +| tst.js:11:9:11:12 | name | +| tst.js:17:9:17:22 | fn | +| tst.js:17:9:17:22 | fn | +| tst.js:17:14:17:22 | obj[name] | +| tst.js:17:14:17:22 | obj[name] | +| tst.js:17:18:17:21 | name | +| tst.js:18:5:18:6 | fn | +| tst.js:18:5:18:6 | fn | +| tst.js:19:9:19:31 | fn | +| tst.js:20:7:20:8 | fn | +| tst.js:21:7:21:15 | obj[name] | +| tst.js:21:7:21:15 | obj[name] | +| tst.js:21:11:21:14 | name | +| tst.js:22:11:22:12 | fn | +| tst.js:26:7:26:15 | obj[name] | +| tst.js:26:7:26:15 | obj[name] | +| tst.js:26:11:26:14 | name | +| tst.js:28:7:28:15 | obj[name] | +| tst.js:28:7:28:15 | obj[name] | +| tst.js:28:11:28:14 | name | +| tst.js:47:39:47:40 | ev | +| tst.js:48:9:48:39 | name | +| tst.js:48:16:48:34 | JSON.parse(ev.data) | +| tst.js:48:16:48:39 | JSON.pa ... a).name | +| tst.js:48:27:48:28 | ev | +| tst.js:48:27:48:33 | ev.data | +| tst.js:49:9:49:23 | fn | +| tst.js:49:14:49:23 | obj2[name] | +| tst.js:49:19:49:22 | name | +| tst.js:50:5:50:6 | fn | +edges +| UnsafeDynamicMethodAccess.js:5:37:5:38 | ev | UnsafeDynamicMethodAccess.js:6:30:6:31 | ev | +| UnsafeDynamicMethodAccess.js:6:9:6:37 | message | UnsafeDynamicMethodAccess.js:15:9:15:15 | message | +| UnsafeDynamicMethodAccess.js:6:19:6:37 | JSON.parse(ev.data) | UnsafeDynamicMethodAccess.js:6:9:6:37 | message | +| UnsafeDynamicMethodAccess.js:6:30:6:31 | ev | UnsafeDynamicMethodAccess.js:6:30:6:36 | ev.data | +| UnsafeDynamicMethodAccess.js:6:30:6:36 | ev.data | UnsafeDynamicMethodAccess.js:6:19:6:37 | JSON.parse(ev.data) | +| UnsafeDynamicMethodAccess.js:15:9:15:15 | message | UnsafeDynamicMethodAccess.js:15:9:15:20 | message.name | +| UnsafeDynamicMethodAccess.js:15:9:15:20 | message.name | UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | +| UnsafeDynamicMethodAccess.js:15:9:15:20 | message.name | UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | +| UnvalidatedDynamicMethodCall.js:14:7:14:41 | action | UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | +| UnvalidatedDynamicMethodCall.js:14:7:14:41 | action | UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | +| UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] | UnvalidatedDynamicMethodCall.js:14:7:14:41 | action | +| UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] | UnvalidatedDynamicMethodCall.js:14:7:14:41 | action | +| UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] | +| UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | UnvalidatedDynamicMethodCall.js:14:16:14:41 | actions ... action] | +| tst.js:6:39:6:40 | ev | tst.js:7:27:7:28 | ev | +| tst.js:6:39:6:40 | ev | tst.js:9:9:9:10 | ev | +| tst.js:7:9:7:39 | name | tst.js:11:9:11:12 | name | +| tst.js:7:9:7:39 | name | tst.js:17:18:17:21 | name | +| tst.js:7:9:7:39 | name | tst.js:21:11:21:14 | name | +| tst.js:7:9:7:39 | name | tst.js:26:11:26:14 | name | +| tst.js:7:9:7:39 | name | tst.js:28:11:28:14 | name | +| tst.js:7:16:7:34 | JSON.parse(ev.data) | tst.js:7:16:7:39 | JSON.pa ... a).name | +| tst.js:7:16:7:39 | JSON.pa ... a).name | tst.js:7:9:7:39 | name | +| tst.js:7:27:7:28 | ev | tst.js:7:27:7:33 | ev.data | +| tst.js:7:27:7:33 | ev.data | tst.js:7:16:7:34 | JSON.parse(ev.data) | +| tst.js:9:9:9:10 | ev | tst.js:9:9:9:15 | ev.data | +| tst.js:9:9:9:15 | ev.data | tst.js:9:5:9:16 | obj[ev.data] | +| tst.js:9:9:9:15 | ev.data | tst.js:9:5:9:16 | obj[ev.data] | +| tst.js:11:9:11:12 | name | tst.js:11:5:11:13 | obj[name] | +| tst.js:11:9:11:12 | name | tst.js:11:5:11:13 | obj[name] | +| tst.js:17:9:17:22 | fn | tst.js:18:5:18:6 | fn | +| tst.js:17:9:17:22 | fn | tst.js:18:5:18:6 | fn | +| tst.js:17:9:17:22 | fn | tst.js:19:9:19:31 | fn | +| tst.js:17:14:17:22 | obj[name] | tst.js:17:9:17:22 | fn | +| tst.js:17:14:17:22 | obj[name] | tst.js:17:9:17:22 | fn | +| tst.js:17:18:17:21 | name | tst.js:17:14:17:22 | obj[name] | +| tst.js:17:18:17:21 | name | tst.js:17:14:17:22 | obj[name] | +| tst.js:19:9:19:31 | fn | tst.js:20:7:20:8 | fn | +| tst.js:19:9:19:31 | fn | tst.js:22:11:22:12 | fn | +| tst.js:21:11:21:14 | name | tst.js:21:7:21:15 | obj[name] | +| tst.js:21:11:21:14 | name | tst.js:21:7:21:15 | obj[name] | +| tst.js:26:11:26:14 | name | tst.js:26:7:26:15 | obj[name] | +| tst.js:26:11:26:14 | name | tst.js:26:7:26:15 | obj[name] | +| tst.js:28:11:28:14 | name | tst.js:28:7:28:15 | obj[name] | +| tst.js:28:11:28:14 | name | tst.js:28:7:28:15 | obj[name] | +| tst.js:47:39:47:40 | ev | tst.js:48:27:48:28 | ev | +| tst.js:48:9:48:39 | name | tst.js:49:19:49:22 | name | +| tst.js:48:16:48:34 | JSON.parse(ev.data) | tst.js:48:16:48:39 | JSON.pa ... a).name | +| tst.js:48:16:48:39 | JSON.pa ... a).name | tst.js:48:9:48:39 | name | +| tst.js:48:27:48:28 | ev | tst.js:48:27:48:33 | ev.data | +| tst.js:48:27:48:33 | ev.data | tst.js:48:16:48:34 | JSON.parse(ev.data) | +| tst.js:49:9:49:23 | fn | tst.js:50:5:50:6 | fn | +| tst.js:49:14:49:23 | obj2[name] | tst.js:49:9:49:23 | fn | +| tst.js:49:19:49:22 | name | tst.js:49:14:49:23 | obj2[name] | +#select +| UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | UnsafeDynamicMethodAccess.js:5:37:5:38 | ev | UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | UnsafeDynamicMethodAccess.js:5:37:5:38 | ev | user-controlled | +| UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | UnsafeDynamicMethodAccess.js:5:37:5:38 | ev | UnsafeDynamicMethodAccess.js:15:5:15:21 | obj[message.name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | UnsafeDynamicMethodAccess.js:5:37:5:38 | ev | user-controlled | +| UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | user-controlled | +| UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | UnvalidatedDynamicMethodCall.js:15:11:15:16 | action | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | UnvalidatedDynamicMethodCall.js:14:24:14:40 | req.params.action | user-controlled | +| tst.js:9:5:9:16 | obj[ev.data] | tst.js:6:39:6:40 | ev | tst.js:9:5:9:16 | obj[ev.data] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:9:5:9:16 | obj[ev.data] | tst.js:6:39:6:40 | ev | tst.js:9:5:9:16 | obj[ev.data] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:11:5:11:13 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:11:5:11:13 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:11:5:11:13 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:11:5:11:13 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:18:5:18:6 | fn | tst.js:6:39:6:40 | ev | tst.js:18:5:18:6 | fn | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:18:5:18:6 | fn | tst.js:6:39:6:40 | ev | tst.js:18:5:18:6 | fn | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:20:7:20:8 | fn | tst.js:6:39:6:40 | ev | tst.js:20:7:20:8 | fn | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:21:7:21:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:21:7:21:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:21:7:21:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:21:7:21:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:22:11:22:12 | fn | tst.js:6:39:6:40 | ev | tst.js:22:11:22:12 | fn | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:26:7:26:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:26:7:26:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:26:7:26:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:26:7:26:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:28:7:28:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:28:7:28:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:28:7:28:15 | obj[name] | tst.js:6:39:6:40 | ev | tst.js:28:7:28:15 | obj[name] | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:6:39:6:40 | ev | user-controlled | +| tst.js:50:5:50:6 | fn | tst.js:47:39:47:40 | ev | tst.js:50:5:50:6 | fn | Invocation of method with $@ name may dispatch to unexpected target and cause an exception. | tst.js:47:39:47:40 | ev | user-controlled | diff --git a/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCall.js b/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCall.js new file mode 100644 index 000000000000..b7d99c2c9eec --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCall.js @@ -0,0 +1,16 @@ +var express = require('express'); +var app = express(); + +var actions = { + play(data) { + // ... + }, + pause(data) { + // ... + } +} + +app.get('/perform/:action/:payload', function(req, res) { + let action = actions[req.params.action]; + res.end(action(req.params.payload)); +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCall.qlref b/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCall.qlref new file mode 100644 index 000000000000..37cbe7fb20e8 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCall.qlref @@ -0,0 +1 @@ +Security/CWE-754/UnvalidatedDynamicMethodCall.ql diff --git a/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCallGood.js b/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCallGood.js new file mode 100644 index 000000000000..9d91149e3846 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCallGood.js @@ -0,0 +1,19 @@ +var express = require('express'); +var app = express(); + +var actions = new Map(); +actions.put("play", function (data) { + // ... +}); +actions.put("pause", function(data) { + // ... +}); + +app.get('/perform/:action/:payload', function(req, res) { + if (actions.has(req.params.action)) { + let action = actions.get(req.params.action); + res.end(action(req.params.payload)); + } else { + res.end("Unsupported action."); + } +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCallGood2.js b/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCallGood2.js new file mode 100644 index 000000000000..07f6f756f06a --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-754/UnvalidatedDynamicMethodCallGood2.js @@ -0,0 +1,22 @@ +var express = require('express'); +var app = express(); + +var actions = { + play(data) { + // ... + }, + pause(data) { + // ... + } +} + +app.get('/perform/:action/:payload', function(req, res) { + if (actions.hasOwnProperty(req.params.action)) { + let action = actions[req.params.action]; + if (typeof action === 'function') { + res.end(action(req.params.payload)); + return; + } + } + res.end("Unsupported action."); +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-754/tst.js b/javascript/ql/test/query-tests/Security/CWE-754/tst.js new file mode 100644 index 000000000000..a75da3ed6dac --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-754/tst.js @@ -0,0 +1,54 @@ +(function() { + let obj = { + foo() {} + }; + + window.addEventListener('message', (ev) => { + let name = JSON.parse(ev.data).name; + + obj[ev.data](); // NOT OK: might not be a function + + obj[name](); // NOT OK: might not be a function + + try { + obj[name](); // OK: exception is caught + } catch(e) {} + + let fn = obj[name]; + fn(); // NOT OK: might not be a function + if (typeof fn == 'function') { + fn(); // NOT OK: might be `valueOf` + obj[name](); // NOT OK: might be `__defineSetter__` + new fn(); // NOT OK: might be `valueOf` or `toString` + } + + if (obj[name]) + obj[name](); // NOT OK + if (typeof obj[name] === 'function') + obj[name](); // NOT OK + + if (obj.hasOwnProperty(name)) { + obj[name](); // NOT OK, but not flagged + } + + let key = "$" + name; + obj[key](); // NOT OK, but not flagged + if (typeof obj[key] === 'function') + obj[key](); // OK + + if (typeof fn === 'function') { + fn.apply(obj); // OK + } + }); + + let obj2 = Object.create(null); + obj2.foo = function() {}; + + window.addEventListener('message', (ev) => { + let name = JSON.parse(ev.data).name; + let fn = obj2[name]; + fn(); // NOT OK: might not be a function + if (typeof fn == 'function') + fn(); // OK: cannot be from prototype + }); +})(); diff --git a/javascript/upgrades/6486c78671c40e4dc07932d806366f09051bb399/old.dbscheme b/javascript/upgrades/6486c78671c40e4dc07932d806366f09051bb399/old.dbscheme new file mode 100644 index 000000000000..6486c78671c4 --- /dev/null +++ b/javascript/upgrades/6486c78671c40e4dc07932d806366f09051bb399/old.dbscheme @@ -0,0 +1,1080 @@ +/*** Standard fragments ***/ + +/** Files and folders **/ + +@location = @location_default; + +locations_default(unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref + ); + +@sourceline = @locatable; + +numlines(int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref + ); + + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files(unique int id: @file, + varchar(900) name: string ref, + varchar(900) simple: string ref, + varchar(900) ext: string ref, + int fromSource: int ref); + +folders(unique int id: @folder, + varchar(900) name: string ref, + varchar(900) simple: string ref); + + +@container = @folder | @file ; + + +containerparent(int parent: @container ref, + unique int child: @container ref); + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +/** Version control data **/ + +svnentries( + int id : @svnentry, + varchar(500) revision : string ref, + varchar(500) author : string ref, + date revisionDate : date ref, + int changeSize : int ref +); + +svnaffectedfiles( + int id : @svnentry ref, + int file : @file ref, + varchar(500) action : string ref +); + +svnentrymsg( + int id : @svnentry ref, + varchar(500) message : string ref +); + +svnchurn( + int commit : @svnentry ref, + int file : @file ref, + int addedLines : int ref, + int deletedLines : int ref +); + + +/*** JavaScript-specific part ***/ + +filetype( + int file: @file ref, + string filetype: string ref +) + +// top-level code fragments +toplevels (unique int id: @toplevel, + int kind: int ref); + +isExterns (int toplevel: @toplevel ref); + +case @toplevel.kind of + 0 = @script +| 1 = @inline_script +| 2 = @event_handler +| 3 = @javascript_url; + +isModule (int tl: @toplevel ref); +isNodejs (int tl: @toplevel ref); + +// statements +#keyset[parent, idx] +stmts (unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +stmtContainers (unique int stmt: @stmt ref, + int container: @stmt_container ref); + +jumpTargets (unique int jump: @stmt ref, + int target: @stmt ref); + +@stmtparent = @stmt | @toplevel | @functionexpr | @arrowfunctionexpr; +@stmt_container = @toplevel | @function | @namespacedeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration; + +case @stmt.kind of + 0 = @emptystmt +| 1 = @blockstmt +| 2 = @exprstmt +| 3 = @ifstmt +| 4 = @labeledstmt +| 5 = @breakstmt +| 6 = @continuestmt +| 7 = @withstmt +| 8 = @switchstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @trystmt +| 12 = @whilestmt +| 13 = @dowhilestmt +| 14 = @forstmt +| 15 = @forinstmt +| 16 = @debuggerstmt +| 17 = @functiondeclstmt +| 18 = @vardeclstmt +| 19 = @case +| 20 = @catchclause +| 21 = @forofstmt +| 22 = @constdeclstmt +| 23 = @letstmt +| 24 = @legacy_letstmt +| 25 = @foreachstmt +| 26 = @classdeclstmt +| 27 = @importdeclaration +| 28 = @exportalldeclaration +| 29 = @exportdefaultdeclaration +| 30 = @exportnameddeclaration +| 31 = @namespacedeclaration +| 32 = @importequalsdeclaration +| 33 = @exportassigndeclaration +| 34 = @interfacedeclaration +| 35 = @typealiasdeclaration +| 36 = @enumdeclaration +| 37 = @externalmoduledeclaration +| 38 = @exportasnamespacedeclaration +| 39 = @globalaugmentationdeclaration +; + +@declstmt = @vardeclstmt | @constdeclstmt | @letstmt | @legacy_letstmt; + +@exportdeclaration = @exportalldeclaration | @exportdefaultdeclaration | @exportnameddeclaration; + +@namespacedefinition = @namespacedeclaration | @enumdeclaration; +@typedefinition = @classdefinition | @interfacedeclaration | @enumdeclaration | @typealiasdeclaration | @enum_member; + +isInstantiated(unique int decl: @namespacedeclaration ref); + +@declarablestmt = @declstmt | @namespacedeclaration | @classdeclstmt | @functiondeclstmt | @enumdeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration; +hasDeclareKeyword(unique int stmt: @declarablestmt ref); + +isForAwaitOf(unique int forof: @forofstmt ref); + +// expressions +#keyset[parent, idx] +exprs (unique int id: @expr, + int kind: int ref, + int parent: @exprparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @exprortype ref); + +enclosingStmt (unique int expr: @exprortype ref, + int stmt: @stmt ref); + +exprContainers (unique int expr: @exprortype ref, + int container: @stmt_container ref); + +arraySize (unique int ae: @arraylike ref, + int sz: int ref); + +isDelegating (int yield: @yieldexpr ref); + +@exprorstmt = @expr | @stmt; +@exprortype = @expr | @typeexpr; +@exprparent = @exprorstmt | @property | @functiontypeexpr; +@arraylike = @arrayexpr | @arraypattern; + +case @expr.kind of + 0 = @label +| 1 = @nullliteral +| 2 = @booleanliteral +| 3 = @numberliteral +| 4 = @stringliteral +| 5 = @regexpliteral +| 6 = @thisexpr +| 7 = @arrayexpr +| 8 = @objexpr +| 9 = @functionexpr +| 10 = @seqexpr +| 11 = @conditionalexpr +| 12 = @newexpr +| 13 = @callexpr +| 14 = @dotexpr +| 15 = @indexexpr +| 16 = @negexpr +| 17 = @plusexpr +| 18 = @lognotexpr +| 19 = @bitnotexpr +| 20 = @typeofexpr +| 21 = @voidexpr +| 22 = @deleteexpr +| 23 = @eqexpr +| 24 = @neqexpr +| 25 = @eqqexpr +| 26 = @neqqexpr +| 27 = @ltexpr +| 28 = @leexpr +| 29 = @gtexpr +| 30 = @geexpr +| 31 = @lshiftexpr +| 32 = @rshiftexpr +| 33 = @urshiftexpr +| 34 = @addexpr +| 35 = @subexpr +| 36 = @mulexpr +| 37 = @divexpr +| 38 = @modexpr +| 39 = @bitorexpr +| 40 = @xorexpr +| 41 = @bitandexpr +| 42 = @inexpr +| 43 = @instanceofexpr +| 44 = @logandexpr +| 45 = @logorexpr +| 47 = @assignexpr +| 48 = @assignaddexpr +| 49 = @assignsubexpr +| 50 = @assignmulexpr +| 51 = @assigndivexpr +| 52 = @assignmodexpr +| 53 = @assignlshiftexpr +| 54 = @assignrshiftexpr +| 55 = @assignurshiftexpr +| 56 = @assignorexpr +| 57 = @assignxorexpr +| 58 = @assignandexpr +| 59 = @preincexpr +| 60 = @postincexpr +| 61 = @predecexpr +| 62 = @postdecexpr +| 63 = @parexpr +| 64 = @vardeclarator +| 65 = @arrowfunctionexpr +| 66 = @spreadelement +| 67 = @arraypattern +| 68 = @objectpattern +| 69 = @yieldexpr +| 70 = @taggedtemplateexpr +| 71 = @templateliteral +| 72 = @templateelement +| 73 = @arraycomprehensionexpr +| 74 = @generatorexpr +| 75 = @forincomprehensionblock +| 76 = @forofcomprehensionblock +| 77 = @legacy_letexpr +| 78 = @vardecl +| 79 = @proper_varaccess +| 80 = @classexpr +| 81 = @superexpr +| 82 = @newtargetexpr +| 83 = @namedimportspecifier +| 84 = @importdefaultspecifier +| 85 = @importnamespacespecifier +| 86 = @namedexportspecifier +| 87 = @expexpr +| 88 = @assignexpexpr +| 89 = @jsxelement +| 90 = @jsxqualifiedname +| 91 = @jsxemptyexpr +| 92 = @awaitexpr +| 93 = @functionsentexpr +| 94 = @decorator +| 95 = @exportdefaultspecifier +| 96 = @exportnamespacespecifier +| 97 = @bindexpr +| 98 = @externalmodulereference +| 99 = @dynamicimport +| 100 = @expressionwithtypearguments +| 101 = @prefixtypeassertion +| 102 = @astypeassertion +| 103 = @export_varaccess +| 104 = @decorator_list +| 105 = @non_null_assertion +| 106 = @bigintliteral +; + +@varaccess = @proper_varaccess | @export_varaccess; +@varref = @vardecl | @varaccess; + +@identifier = @label | @varref | @typeidentifier; + +@literal = @nullliteral | @booleanliteral | @numberliteral | @stringliteral | @regexpliteral | @bigintliteral; + +@propaccess = @dotexpr | @indexexpr; + +@invokeexpr = @newexpr | @callexpr; + +@unaryexpr = @negexpr | @plusexpr | @lognotexpr | @bitnotexpr | @typeofexpr | @voidexpr | @deleteexpr | @spreadelement; + +@equalitytest = @eqexpr | @neqexpr | @eqqexpr | @neqqexpr; + +@comparison = @equalitytest | @ltexpr | @leexpr | @gtexpr | @geexpr; + +@binaryexpr = @comparison | @lshiftexpr | @rshiftexpr | @urshiftexpr | @addexpr | @subexpr | @mulexpr | @divexpr | @modexpr | @expexpr | @bitorexpr | @xorexpr | @bitandexpr | @inexpr | @instanceofexpr | @logandexpr | @logorexpr; + +@assignment = @assignexpr | @assignaddexpr | @assignsubexpr | @assignmulexpr | @assigndivexpr | @assignmodexpr | @assignexpexpr | @assignlshiftexpr | @assignrshiftexpr | @assignurshiftexpr | @assignorexpr | @assignxorexpr | @assignandexpr; + +@updateexpr = @preincexpr | @postincexpr | @predecexpr | @postdecexpr; + +@pattern = @varref | @arraypattern | @objectpattern; + +@comprehensionexpr = @arraycomprehensionexpr | @generatorexpr; + +@comprehensionblock = @forincomprehensionblock | @forofcomprehensionblock; + +@importspecifier = @namedimportspecifier | @importdefaultspecifier | @importnamespacespecifier; + +@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier; + +@typeassertion = @astypeassertion | @prefixtypeassertion; + +@classdefinition = @classdeclstmt | @classexpr; +@interfacedefinition = @interfacedeclaration | @interfacetypeexpr; +@classorinterface = @classdefinition | @interfacedefinition; + +@lexical_decl = @vardecl | @typedecl; +@lexical_access = @varaccess | @localtypeaccess | @localvartypeaccess | @localnamespaceaccess; +@lexical_ref = @lexical_decl | @lexical_access; + +// scopes +scopes (unique int id: @scope, + int kind: int ref); + +case @scope.kind of + 0 = @globalscope +| 1 = @functionscope +| 2 = @catchscope +| 3 = @modulescope +| 4 = @blockscope +| 5 = @forscope +| 6 = @forinscope // for-of scopes work the same as for-in scopes +| 7 = @comprehensionblockscope +| 8 = @classexprscope +| 9 = @namespacescope +| 10 = @classdeclscope +| 11 = @interfacescope +| 12 = @typealiasscope +| 13 = @mappedtypescope +| 14 = @enumscope +| 15 = @externalmodulescope +| 16 = @conditionaltypescope; + +scopenodes (unique int node: @ast_node ref, + int scope: @scope ref); + +scopenesting (unique int inner: @scope ref, + int outer: @scope ref); + +// functions +@function = @functiondeclstmt | @functionexpr | @arrowfunctionexpr; + +@parameterized = @function | @catchclause; +@type_parameterized = @function | @classorinterface | @typealiasdeclaration | @mappedtypeexpr | @infertypeexpr; + +isGenerator (int fun: @function ref); +hasRestParameter (int fun: @function ref); +isAsync (int fun: @function ref); + +// variables and lexically scoped type names +#keyset[scope, name] +variables (unique int id: @variable, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_type_names (unique int id: @local_type_name, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_namespace_names (unique int id: @local_namespace_name, + varchar(900) name: string ref, + int scope: @scope ref); + +isArgumentsObject (int id: @variable ref); + +@lexical_name = @variable | @local_type_name | @local_namespace_name; + +@bind_id = @varaccess | @localvartypeaccess; +bind (unique int id: @bind_id ref, + int decl: @variable ref); + +decl (unique int id: @vardecl ref, + int decl: @variable ref); + +@typebind_id = @localtypeaccess | @export_varaccess; +typebind (unique int id: @typebind_id ref, + int decl: @local_type_name ref); + +@typedecl_id = @typedecl | @vardecl; +typedecl (unique int id: @typedecl_id ref, + int decl: @local_type_name ref); + +namespacedecl (unique int id: @vardecl ref, + int decl: @local_namespace_name ref); + +@namespacebind_id = @localnamespaceaccess | @export_varaccess; +namespacebind (unique int id: @namespacebind_id ref, + int decl: @local_namespace_name ref); + + +// properties in object literals, property patterns in object patterns, and method declarations in classes +#keyset[parent, index] +properties (unique int id: @property, + int parent: @property_parent ref, + int index: int ref, + int kind: int ref, + varchar(900) tostring: string ref); + +case @property.kind of + 0 = @value_property +| 1 = @property_getter +| 2 = @property_setter +| 3 = @jsx_attribute +| 4 = @function_call_signature +| 5 = @constructor_call_signature +| 6 = @index_signature +| 7 = @enum_member +| 8 = @proper_field +| 9 = @parameter_field +; + +@property_parent = @objexpr | @objectpattern | @classdefinition | @jsxelement | @interfacedefinition | @enumdeclaration; +@property_accessor = @property_getter | @property_setter; +@call_signature = @function_call_signature | @constructor_call_signature; +@field = @proper_field | @parameter_field; +@field_or_vardeclarator = @field | @vardeclarator; + +isComputed (int id: @property ref); +isMethod (int id: @property ref); +isStatic (int id: @property ref); +isAbstractMember (int id: @property ref); +isConstEnum (int id: @enumdeclaration ref); +isAbstractClass (int id: @classdeclstmt ref); + +hasPublicKeyword (int id: @property ref); +hasPrivateKeyword (int id: @property ref); +hasProtectedKeyword (int id: @property ref); +hasReadonlyKeyword (int id: @property ref); +isOptionalMember (int id: @property ref); +hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator ref); + +#keyset[constructor, param_index] +parameter_fields( + unique int field: @parameter_field ref, + int constructor: @functionexpr ref, + int param_index: int ref +); + +// types +#keyset[parent, idx] +typeexprs ( + unique int id: @typeexpr, + int kind: int ref, + int parent: @typeexpr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref +); + +case @typeexpr.kind of + 0 = @localtypeaccess +| 1 = @typedecl +| 2 = @keywordtypeexpr +| 3 = @stringliteraltypeexpr +| 4 = @numberliteraltypeexpr +| 5 = @booleanliteraltypeexpr +| 6 = @arraytypeexpr +| 7 = @uniontypeexpr +| 8 = @indexedaccesstypeexpr +| 9 = @intersectiontypeexpr +| 10 = @parenthesizedtypeexpr +| 11 = @tupletypeexpr +| 12 = @keyoftypeexpr +| 13 = @qualifiedtypeaccess +| 14 = @generictypeexpr +| 15 = @typelabel +| 16 = @typeoftypeexpr +| 17 = @localvartypeaccess +| 18 = @qualifiedvartypeaccess +| 19 = @thisvartypeaccess +| 20 = @istypeexpr +| 21 = @interfacetypeexpr +| 22 = @typeparameter +| 23 = @plainfunctiontypeexpr +| 24 = @constructortypeexpr +| 25 = @localnamespaceaccess +| 26 = @qualifiednamespaceaccess +| 27 = @mappedtypeexpr +| 28 = @conditionaltypeexpr +| 29 = @infertypeexpr +| 30 = @importtypeaccess +| 31 = @importnamespaceaccess +| 32 = @importvartypeaccess +| 33 = @optionaltypeexpr +| 34 = @resttypeexpr +; + +@typeref = @typeaccess | @typedecl; +@typeidentifier = @typedecl | @localtypeaccess | @typelabel | @localvartypeaccess | @localnamespaceaccess; +@typeexpr_parent = @expr | @stmt | @property | @typeexpr; +@literaltypeexpr = @stringliteraltypeexpr | @numberliteraltypeexpr | @booleanliteraltypeexpr; +@typeaccess = @localtypeaccess | @qualifiedtypeaccess | @importtypeaccess; +@vartypeaccess = @localvartypeaccess | @qualifiedvartypeaccess | @thisvartypeaccess | @importvartypeaccess; +@namespaceaccess = @localnamespaceaccess | @qualifiednamespaceaccess | @importnamespaceaccess; +@importtypeexpr = @importtypeaccess | @importnamespaceaccess | @importvartypeaccess; + +@functiontypeexpr = @plainfunctiontypeexpr | @constructortypeexpr; + +// types +types ( + unique int id: @type, + int kind: int ref, + varchar(900) tostring: string ref +); + +#keyset[parent, idx] +type_child ( + int child: @type ref, + int parent: @type ref, + int idx: int ref +); + +case @type.kind of + 0 = @anytype +| 1 = @stringtype +| 2 = @numbertype +| 3 = @uniontype +| 4 = @truetype +| 5 = @falsetype +| 6 = @typereference +| 7 = @objecttype +| 8 = @canonicaltypevariabletype +| 9 = @typeoftype +| 10 = @voidtype +| 11 = @undefinedtype +| 12 = @nulltype +| 13 = @nevertype +| 14 = @plainsymboltype +| 15 = @uniquesymboltype +| 16 = @objectkeywordtype +| 17 = @intersectiontype +| 18 = @tupletype +| 19 = @lexicaltypevariabletype +| 20 = @thistype +| 21 = @numberliteraltype +| 22 = @stringliteraltype +| 23 = @unknowntype +; + +@booleanliteraltype = @truetype | @falsetype; +@symboltype = @plainsymboltype | @uniquesymboltype; +@unionorintersectiontype = @uniontype | @intersectiontype; +@typevariabletype = @canonicaltypevariabletype | @lexicaltypevariabletype; + +@typed_ast_node = @expr | @typeexpr | @function; +ast_node_type( + unique int node: @typed_ast_node ref, + int typ: @type ref); + +invoke_expr_signature( + unique int node: @invokeexpr ref, + int sig: @signature_type ref +); + +invoke_expr_overload_index( + unique int node: @invokeexpr ref, + int index: int ref +); + +symbols ( + unique int id: @symbol, + int kind: int ref, + varchar(900) name: string ref +); + +symbol_parent ( + unique int symbol: @symbol ref, + int parent: @symbol ref +); + +symbol_module ( + int symbol: @symbol ref, + varchar(900) moduleName: string ref +); + +symbol_global ( + int symbol: @symbol ref, + varchar(900) globalName: string ref +); + +case @symbol.kind of + 0 = @root_symbol +| 1 = @member_symbol +| 2 = @other_symbol +; + +@type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype; +@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr; + +ast_node_symbol( + unique int node: @ast_node_with_symbol ref, + int symbol: @symbol ref); + +type_symbol( + unique int typ: @type_with_symbol ref, + int symbol: @symbol ref); + +#keyset[typ, name] +type_property( + int typ: @type ref, + varchar(900) name: string ref, + int propertyType: @type ref); + +@literaltype = @stringliteraltype | @numberliteraltype | @booleanliteraltype; +@type_with_literal_value = @stringliteraltype | @numberliteraltype; +type_literal_value( + unique int typ: @type_with_literal_value ref, + varchar(900) value: string ref); + +signature_types ( + unique int id: @signature_type, + int kind: int ref, + varchar(900) tostring: string ref, + int type_parameters: int ref, + int required_params: int ref +); + +case @signature_type.kind of + 0 = @function_signature_type +| 1 = @constructor_signature_type +; + +#keyset[typ, kind, index] +type_contains_signature ( + int typ: @type ref, + int kind: int ref, // constructor/call/index + int index: int ref, // ordering of overloaded signatures + int sig: @signature_type ref +); + +#keyset[parent, index] +signature_contains_type ( + int child: @type ref, + int parent: @signature_type ref, + int index: int ref +); + +#keyset[sig, index] +signature_parameter_name ( + int sig: @signature_type ref, + int index: int ref, + varchar(900) name: string ref +); + +number_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +string_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +base_type_names( + int typeName: @symbol ref, + int baseTypeName: @symbol ref +); + +self_types( + int typeName: @symbol ref, + int selfType: @typereference ref +); + +tuple_type_min_length( + unique int typ: @type ref, + int minLength: int ref +); + +tuple_type_rest( + unique int typ: @type ref +); + +// comments +comments (unique int id: @comment, + int kind: int ref, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(900) tostring: string ref); + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment +| 2 = @doccomment +| 3 = @htmlcommentstart +| 4 = @htmlcommentend; + +@htmlcomment = @htmlcommentstart | @htmlcommentend; +@linecomment = @slashslashcomment | @htmlcomment; +@blockcomment = @slashstarcomment | @doccomment; + +// source lines +lines (unique int id: @line, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(2) terminator: string ref); +indentation (int file: @file ref, + int lineno: int ref, + varchar(1) indentChar: string ref, + int indentDepth: int ref); + +// JavaScript parse errors +jsParseErrors (unique int id: @js_parse_error, + int toplevel: @toplevel ref, + varchar(900) message: string ref, + varchar(900) line: string ref); + +// regular expressions +#keyset[parent, idx] +regexpterm (unique int id: @regexpterm, + int kind: int ref, + int parent: @regexpparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +@regexpparent = @regexpterm | @regexpliteral; + +case @regexpterm.kind of + 0 = @regexp_alt +| 1 = @regexp_seq +| 2 = @regexp_caret +| 3 = @regexp_dollar +| 4 = @regexp_wordboundary +| 5 = @regexp_nonwordboundary +| 6 = @regexp_positive_lookahead +| 7 = @regexp_negative_lookahead +| 8 = @regexp_star +| 9 = @regexp_plus +| 10 = @regexp_opt +| 11 = @regexp_range +| 12 = @regexp_dot +| 13 = @regexp_group +| 14 = @regexp_normal_char +| 15 = @regexp_hex_escape +| 16 = @regexp_unicode_escape +| 17 = @regexp_dec_escape +| 18 = @regexp_oct_escape +| 19 = @regexp_ctrl_escape +| 20 = @regexp_char_class_escape +| 21 = @regexp_id_escape +| 22 = @regexp_backref +| 23 = @regexp_char_class +| 24 = @regexp_char_range +| 25 = @regexp_positive_lookbehind +| 26 = @regexp_negative_lookbehind +| 27 = @regexp_unicode_property_escape; + +regexpParseErrors (unique int id: @regexp_parse_error, + int regexp: @regexpterm ref, + varchar(900) message: string ref); + +@regexp_quantifier = @regexp_star | @regexp_plus | @regexp_opt | @regexp_range; +@regexp_escape = @regexp_char_escape | @regexp_char_class_escape | @regexp_unicode_property_escape; +@regexp_char_escape = @regexp_hex_escape | @regexp_unicode_escape | @regexp_dec_escape | @regexp_oct_escape | @regexp_ctrl_escape | @regexp_id_escape; +@regexp_constant = @regexp_normal_char | @regexp_char_escape; +@regexp_lookahead = @regexp_positive_lookahead | @regexp_negative_lookahead; +@regexp_lookbehind = @regexp_positive_lookbehind | @regexp_negative_lookbehind; +@regexp_subpattern = @regexp_lookahead | @regexp_lookbehind; + +isGreedy (int id: @regexp_quantifier ref); +rangeQuantifierLowerBound (unique int id: @regexp_range ref, int lo: int ref); +rangeQuantifierUpperBound (unique int id: @regexp_range ref, int hi: int ref); +isCapture (unique int id: @regexp_group ref, int number: int ref); +isNamedCapture (unique int id: @regexp_group ref, string name: string ref); +isInverted (int id: @regexp_char_class ref); +regexpConstValue (unique int id: @regexp_constant ref, varchar(1) value: string ref); +charClassEscape (unique int id: @regexp_char_class_escape ref, varchar(1) value: string ref); +backref (unique int id: @regexp_backref ref, int value: int ref); +namedBackref (unique int id: @regexp_backref ref, string name: string ref); +unicodePropertyEscapeName (unique int id: @regexp_unicode_property_escape ref, string name: string ref); +unicodePropertyEscapeValue (unique int id: @regexp_unicode_property_escape ref, string value: string ref); + +// tokens +#keyset[toplevel, idx] +tokeninfo (unique int id: @token, + int kind: int ref, + int toplevel: @toplevel ref, + int idx: int ref, + varchar(900) value: string ref); + +case @token.kind of + 0 = @token_eof +| 1 = @token_null_literal +| 2 = @token_boolean_literal +| 3 = @token_numeric_literal +| 4 = @token_string_literal +| 5 = @token_regular_expression +| 6 = @token_identifier +| 7 = @token_keyword +| 8 = @token_punctuator; + +// associate comments with the token immediately following them (which may be EOF) +next_token (int comment: @comment ref, int token: @token ref); + +// JSON +#keyset[parent, idx] +json (unique int id: @json_value, + int kind: int ref, + int parent: @json_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +json_literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @json_value ref); + +json_properties (int obj: @json_object ref, + varchar(900) property: string ref, + int value: @json_value ref); + +json_errors (unique int id: @json_parse_error, + varchar(900) message: string ref); + +case @json_value.kind of + 0 = @json_null +| 1 = @json_boolean +| 2 = @json_number +| 3 = @json_string +| 4 = @json_array +| 5 = @json_object; + +@json_parent = @json_object | @json_array | @file; + +// locations +@ast_node = @toplevel | @stmt | @expr | @property | @typeexpr; + +@locatable = @file + | @ast_node + | @comment + | @line + | @js_parse_error | @regexp_parse_error | @json_parse_error + | @regexpterm + | @json_value + | @token + | @cfg_node + | @jsdoc | @jsdoc_type_expr | @jsdoc_tag + | @yaml_node | @yaml_error + | @xmllocatable; + +hasLocation (unique int locatable: @locatable ref, + int location: @location ref); + +// CFG +entry_cfg_node (unique int id: @entry_node, int container: @stmt_container ref); +exit_cfg_node (unique int id: @exit_node, int container: @stmt_container ref); +guard_node (unique int id: @guard_node, int kind: int ref, int test: @expr ref); +case @guard_node.kind of + 0 = @falsy_guard +| 1 = @truthy_guard; +@condition_guard = @falsy_guard | @truthy_guard; + +@synthetic_cfg_node = @entry_node | @exit_node | @guard_node; +@cfg_node = @synthetic_cfg_node | @exprparent; + +successor (int pred: @cfg_node ref, int succ: @cfg_node ref); + +// JSDoc comments +jsdoc (unique int id: @jsdoc, varchar(900) description: string ref, int comment: @comment ref); +#keyset[parent, idx] +jsdoc_tags (unique int id: @jsdoc_tag, varchar(900) title: string ref, + int parent: @jsdoc ref, int idx: int ref, varchar(900) tostring: string ref); +jsdoc_tag_descriptions (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); +jsdoc_tag_names (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); + +#keyset[parent, idx] +jsdoc_type_exprs (unique int id: @jsdoc_type_expr, + int kind: int ref, + int parent: @jsdoc_type_expr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); +case @jsdoc_type_expr.kind of + 0 = @jsdoc_any_type_expr +| 1 = @jsdoc_null_type_expr +| 2 = @jsdoc_undefined_type_expr +| 3 = @jsdoc_unknown_type_expr +| 4 = @jsdoc_void_type_expr +| 5 = @jsdoc_named_type_expr +| 6 = @jsdoc_applied_type_expr +| 7 = @jsdoc_nullable_type_expr +| 8 = @jsdoc_non_nullable_type_expr +| 9 = @jsdoc_record_type_expr +| 10 = @jsdoc_array_type_expr +| 11 = @jsdoc_union_type_expr +| 12 = @jsdoc_function_type_expr +| 13 = @jsdoc_optional_type_expr +| 14 = @jsdoc_rest_type_expr +; + +#keyset[id, idx] +jsdoc_record_field_name (int id: @jsdoc_record_type_expr ref, int idx: int ref, varchar(900) name: string ref); +jsdoc_prefix_qualifier (int id: @jsdoc_type_expr ref); +jsdoc_has_new_parameter (int fn: @jsdoc_function_type_expr ref); + +@jsdoc_type_expr_parent = @jsdoc_type_expr | @jsdoc_tag; + +jsdoc_errors (unique int id: @jsdoc_error, int tag: @jsdoc_tag ref, varchar(900) message: string ref, varchar(900) tostring: string ref); + +// YAML +#keyset[parent, idx] +yaml (unique int id: @yaml_node, + int kind: int ref, + int parent: @yaml_node_parent ref, + int idx: int ref, + varchar(900) tag: string ref, + varchar(900) tostring: string ref); + +case @yaml_node.kind of + 0 = @yaml_scalar_node +| 1 = @yaml_mapping_node +| 2 = @yaml_sequence_node +| 3 = @yaml_alias_node +; + +@yaml_collection_node = @yaml_mapping_node | @yaml_sequence_node; + +@yaml_node_parent = @yaml_collection_node | @file; + +yaml_anchors (unique int node: @yaml_node ref, + varchar(900) anchor: string ref); + +yaml_aliases (unique int alias: @yaml_alias_node ref, + varchar(900) target: string ref); + +yaml_scalars (unique int scalar: @yaml_scalar_node ref, + int style: int ref, + varchar(900) value: string ref); + +yaml_errors (unique int id: @yaml_error, + varchar(900) message: string ref); + +/* XML Files */ + +xmlEncoding( + unique int id: @file ref, + varchar(900) encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + varchar(900) root: string ref, + varchar(900) publicId: string ref, + varchar(900) systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + varchar(900) name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + varchar(900) name: string ref, + varchar(3600) value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + varchar(900) prefixName: string ref, + varchar(900) URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +@dataflownode = @expr | @functiondeclstmt | @classdeclstmt | @namespacedeclaration | @enumdeclaration | @property; + +/* Last updated 2017/07/11. */ diff --git a/javascript/upgrades/6486c78671c40e4dc07932d806366f09051bb399/semmlecode.javascript.dbscheme b/javascript/upgrades/6486c78671c40e4dc07932d806366f09051bb399/semmlecode.javascript.dbscheme new file mode 100644 index 000000000000..81e6619c681f --- /dev/null +++ b/javascript/upgrades/6486c78671c40e4dc07932d806366f09051bb399/semmlecode.javascript.dbscheme @@ -0,0 +1,1084 @@ +/*** Standard fragments ***/ + +/** Files and folders **/ + +@location = @location_default; + +locations_default(unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref + ); + +@sourceline = @locatable; + +numlines(int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref + ); + + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files(unique int id: @file, + varchar(900) name: string ref, + varchar(900) simple: string ref, + varchar(900) ext: string ref, + int fromSource: int ref); + +folders(unique int id: @folder, + varchar(900) name: string ref, + varchar(900) simple: string ref); + + +@container = @folder | @file ; + + +containerparent(int parent: @container ref, + unique int child: @container ref); + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +/** Version control data **/ + +svnentries( + int id : @svnentry, + varchar(500) revision : string ref, + varchar(500) author : string ref, + date revisionDate : date ref, + int changeSize : int ref +); + +svnaffectedfiles( + int id : @svnentry ref, + int file : @file ref, + varchar(500) action : string ref +); + +svnentrymsg( + int id : @svnentry ref, + varchar(500) message : string ref +); + +svnchurn( + int commit : @svnentry ref, + int file : @file ref, + int addedLines : int ref, + int deletedLines : int ref +); + + +/*** JavaScript-specific part ***/ + +filetype( + int file: @file ref, + string filetype: string ref +) + +// top-level code fragments +toplevels (unique int id: @toplevel, + int kind: int ref); + +isExterns (int toplevel: @toplevel ref); + +case @toplevel.kind of + 0 = @script +| 1 = @inline_script +| 2 = @event_handler +| 3 = @javascript_url; + +isModule (int tl: @toplevel ref); +isNodejs (int tl: @toplevel ref); + +// statements +#keyset[parent, idx] +stmts (unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +stmtContainers (unique int stmt: @stmt ref, + int container: @stmt_container ref); + +jumpTargets (unique int jump: @stmt ref, + int target: @stmt ref); + +@stmtparent = @stmt | @toplevel | @functionexpr | @arrowfunctionexpr; +@stmt_container = @toplevel | @function | @namespacedeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration; + +case @stmt.kind of + 0 = @emptystmt +| 1 = @blockstmt +| 2 = @exprstmt +| 3 = @ifstmt +| 4 = @labeledstmt +| 5 = @breakstmt +| 6 = @continuestmt +| 7 = @withstmt +| 8 = @switchstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @trystmt +| 12 = @whilestmt +| 13 = @dowhilestmt +| 14 = @forstmt +| 15 = @forinstmt +| 16 = @debuggerstmt +| 17 = @functiondeclstmt +| 18 = @vardeclstmt +| 19 = @case +| 20 = @catchclause +| 21 = @forofstmt +| 22 = @constdeclstmt +| 23 = @letstmt +| 24 = @legacy_letstmt +| 25 = @foreachstmt +| 26 = @classdeclstmt +| 27 = @importdeclaration +| 28 = @exportalldeclaration +| 29 = @exportdefaultdeclaration +| 30 = @exportnameddeclaration +| 31 = @namespacedeclaration +| 32 = @importequalsdeclaration +| 33 = @exportassigndeclaration +| 34 = @interfacedeclaration +| 35 = @typealiasdeclaration +| 36 = @enumdeclaration +| 37 = @externalmoduledeclaration +| 38 = @exportasnamespacedeclaration +| 39 = @globalaugmentationdeclaration +; + +@declstmt = @vardeclstmt | @constdeclstmt | @letstmt | @legacy_letstmt; + +@exportdeclaration = @exportalldeclaration | @exportdefaultdeclaration | @exportnameddeclaration; + +@namespacedefinition = @namespacedeclaration | @enumdeclaration; +@typedefinition = @classdefinition | @interfacedeclaration | @enumdeclaration | @typealiasdeclaration | @enum_member; + +isInstantiated(unique int decl: @namespacedeclaration ref); + +@declarablestmt = @declstmt | @namespacedeclaration | @classdeclstmt | @functiondeclstmt | @enumdeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration; +hasDeclareKeyword(unique int stmt: @declarablestmt ref); + +isForAwaitOf(unique int forof: @forofstmt ref); + +// expressions +#keyset[parent, idx] +exprs (unique int id: @expr, + int kind: int ref, + int parent: @exprparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @exprortype ref); + +enclosingStmt (unique int expr: @exprortype ref, + int stmt: @stmt ref); + +exprContainers (unique int expr: @exprortype ref, + int container: @stmt_container ref); + +arraySize (unique int ae: @arraylike ref, + int sz: int ref); + +isDelegating (int yield: @yieldexpr ref); + +@exprorstmt = @expr | @stmt; +@exprortype = @expr | @typeexpr; +@exprparent = @exprorstmt | @property | @functiontypeexpr; +@arraylike = @arrayexpr | @arraypattern; + +case @expr.kind of + 0 = @label +| 1 = @nullliteral +| 2 = @booleanliteral +| 3 = @numberliteral +| 4 = @stringliteral +| 5 = @regexpliteral +| 6 = @thisexpr +| 7 = @arrayexpr +| 8 = @objexpr +| 9 = @functionexpr +| 10 = @seqexpr +| 11 = @conditionalexpr +| 12 = @newexpr +| 13 = @callexpr +| 14 = @dotexpr +| 15 = @indexexpr +| 16 = @negexpr +| 17 = @plusexpr +| 18 = @lognotexpr +| 19 = @bitnotexpr +| 20 = @typeofexpr +| 21 = @voidexpr +| 22 = @deleteexpr +| 23 = @eqexpr +| 24 = @neqexpr +| 25 = @eqqexpr +| 26 = @neqqexpr +| 27 = @ltexpr +| 28 = @leexpr +| 29 = @gtexpr +| 30 = @geexpr +| 31 = @lshiftexpr +| 32 = @rshiftexpr +| 33 = @urshiftexpr +| 34 = @addexpr +| 35 = @subexpr +| 36 = @mulexpr +| 37 = @divexpr +| 38 = @modexpr +| 39 = @bitorexpr +| 40 = @xorexpr +| 41 = @bitandexpr +| 42 = @inexpr +| 43 = @instanceofexpr +| 44 = @logandexpr +| 45 = @logorexpr +| 47 = @assignexpr +| 48 = @assignaddexpr +| 49 = @assignsubexpr +| 50 = @assignmulexpr +| 51 = @assigndivexpr +| 52 = @assignmodexpr +| 53 = @assignlshiftexpr +| 54 = @assignrshiftexpr +| 55 = @assignurshiftexpr +| 56 = @assignorexpr +| 57 = @assignxorexpr +| 58 = @assignandexpr +| 59 = @preincexpr +| 60 = @postincexpr +| 61 = @predecexpr +| 62 = @postdecexpr +| 63 = @parexpr +| 64 = @vardeclarator +| 65 = @arrowfunctionexpr +| 66 = @spreadelement +| 67 = @arraypattern +| 68 = @objectpattern +| 69 = @yieldexpr +| 70 = @taggedtemplateexpr +| 71 = @templateliteral +| 72 = @templateelement +| 73 = @arraycomprehensionexpr +| 74 = @generatorexpr +| 75 = @forincomprehensionblock +| 76 = @forofcomprehensionblock +| 77 = @legacy_letexpr +| 78 = @vardecl +| 79 = @proper_varaccess +| 80 = @classexpr +| 81 = @superexpr +| 82 = @newtargetexpr +| 83 = @namedimportspecifier +| 84 = @importdefaultspecifier +| 85 = @importnamespacespecifier +| 86 = @namedexportspecifier +| 87 = @expexpr +| 88 = @assignexpexpr +| 89 = @jsxelement +| 90 = @jsxqualifiedname +| 91 = @jsxemptyexpr +| 92 = @awaitexpr +| 93 = @functionsentexpr +| 94 = @decorator +| 95 = @exportdefaultspecifier +| 96 = @exportnamespacespecifier +| 97 = @bindexpr +| 98 = @externalmodulereference +| 99 = @dynamicimport +| 100 = @expressionwithtypearguments +| 101 = @prefixtypeassertion +| 102 = @astypeassertion +| 103 = @export_varaccess +| 104 = @decorator_list +| 105 = @non_null_assertion +| 106 = @bigintliteral +; + +@varaccess = @proper_varaccess | @export_varaccess; +@varref = @vardecl | @varaccess; + +@identifier = @label | @varref | @typeidentifier; + +@literal = @nullliteral | @booleanliteral | @numberliteral | @stringliteral | @regexpliteral | @bigintliteral; + +@propaccess = @dotexpr | @indexexpr; + +@invokeexpr = @newexpr | @callexpr; + +@unaryexpr = @negexpr | @plusexpr | @lognotexpr | @bitnotexpr | @typeofexpr | @voidexpr | @deleteexpr | @spreadelement; + +@equalitytest = @eqexpr | @neqexpr | @eqqexpr | @neqqexpr; + +@comparison = @equalitytest | @ltexpr | @leexpr | @gtexpr | @geexpr; + +@binaryexpr = @comparison | @lshiftexpr | @rshiftexpr | @urshiftexpr | @addexpr | @subexpr | @mulexpr | @divexpr | @modexpr | @expexpr | @bitorexpr | @xorexpr | @bitandexpr | @inexpr | @instanceofexpr | @logandexpr | @logorexpr; + +@assignment = @assignexpr | @assignaddexpr | @assignsubexpr | @assignmulexpr | @assigndivexpr | @assignmodexpr | @assignexpexpr | @assignlshiftexpr | @assignrshiftexpr | @assignurshiftexpr | @assignorexpr | @assignxorexpr | @assignandexpr; + +@updateexpr = @preincexpr | @postincexpr | @predecexpr | @postdecexpr; + +@pattern = @varref | @arraypattern | @objectpattern; + +@comprehensionexpr = @arraycomprehensionexpr | @generatorexpr; + +@comprehensionblock = @forincomprehensionblock | @forofcomprehensionblock; + +@importspecifier = @namedimportspecifier | @importdefaultspecifier | @importnamespacespecifier; + +@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier; + +@typeassertion = @astypeassertion | @prefixtypeassertion; + +@classdefinition = @classdeclstmt | @classexpr; +@interfacedefinition = @interfacedeclaration | @interfacetypeexpr; +@classorinterface = @classdefinition | @interfacedefinition; + +@lexical_decl = @vardecl | @typedecl; +@lexical_access = @varaccess | @localtypeaccess | @localvartypeaccess | @localnamespaceaccess; +@lexical_ref = @lexical_decl | @lexical_access; + +// scopes +scopes (unique int id: @scope, + int kind: int ref); + +case @scope.kind of + 0 = @globalscope +| 1 = @functionscope +| 2 = @catchscope +| 3 = @modulescope +| 4 = @blockscope +| 5 = @forscope +| 6 = @forinscope // for-of scopes work the same as for-in scopes +| 7 = @comprehensionblockscope +| 8 = @classexprscope +| 9 = @namespacescope +| 10 = @classdeclscope +| 11 = @interfacescope +| 12 = @typealiasscope +| 13 = @mappedtypescope +| 14 = @enumscope +| 15 = @externalmodulescope +| 16 = @conditionaltypescope; + +scopenodes (unique int node: @ast_node ref, + int scope: @scope ref); + +scopenesting (unique int inner: @scope ref, + int outer: @scope ref); + +// functions +@function = @functiondeclstmt | @functionexpr | @arrowfunctionexpr; + +@parameterized = @function | @catchclause; +@type_parameterized = @function | @classorinterface | @typealiasdeclaration | @mappedtypeexpr | @infertypeexpr; + +isGenerator (int fun: @function ref); +hasRestParameter (int fun: @function ref); +isAsync (int fun: @function ref); + +// variables and lexically scoped type names +#keyset[scope, name] +variables (unique int id: @variable, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_type_names (unique int id: @local_type_name, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_namespace_names (unique int id: @local_namespace_name, + varchar(900) name: string ref, + int scope: @scope ref); + +isArgumentsObject (int id: @variable ref); + +@lexical_name = @variable | @local_type_name | @local_namespace_name; + +@bind_id = @varaccess | @localvartypeaccess; +bind (unique int id: @bind_id ref, + int decl: @variable ref); + +decl (unique int id: @vardecl ref, + int decl: @variable ref); + +@typebind_id = @localtypeaccess | @export_varaccess; +typebind (unique int id: @typebind_id ref, + int decl: @local_type_name ref); + +@typedecl_id = @typedecl | @vardecl; +typedecl (unique int id: @typedecl_id ref, + int decl: @local_type_name ref); + +namespacedecl (unique int id: @vardecl ref, + int decl: @local_namespace_name ref); + +@namespacebind_id = @localnamespaceaccess | @export_varaccess; +namespacebind (unique int id: @namespacebind_id ref, + int decl: @local_namespace_name ref); + + +// properties in object literals, property patterns in object patterns, and method declarations in classes +#keyset[parent, index] +properties (unique int id: @property, + int parent: @property_parent ref, + int index: int ref, + int kind: int ref, + varchar(900) tostring: string ref); + +case @property.kind of + 0 = @value_property +| 1 = @property_getter +| 2 = @property_setter +| 3 = @jsx_attribute +| 4 = @function_call_signature +| 5 = @constructor_call_signature +| 6 = @index_signature +| 7 = @enum_member +| 8 = @proper_field +| 9 = @parameter_field +; + +@property_parent = @objexpr | @objectpattern | @classdefinition | @jsxelement | @interfacedefinition | @enumdeclaration; +@property_accessor = @property_getter | @property_setter; +@call_signature = @function_call_signature | @constructor_call_signature; +@field = @proper_field | @parameter_field; +@field_or_vardeclarator = @field | @vardeclarator; + +isComputed (int id: @property ref); +isMethod (int id: @property ref); +isStatic (int id: @property ref); +isAbstractMember (int id: @property ref); +isConstEnum (int id: @enumdeclaration ref); +isAbstractClass (int id: @classdeclstmt ref); + +hasPublicKeyword (int id: @property ref); +hasPrivateKeyword (int id: @property ref); +hasProtectedKeyword (int id: @property ref); +hasReadonlyKeyword (int id: @property ref); +isOptionalMember (int id: @property ref); +hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator ref); + +#keyset[constructor, param_index] +parameter_fields( + unique int field: @parameter_field ref, + int constructor: @functionexpr ref, + int param_index: int ref +); + +// types +#keyset[parent, idx] +typeexprs ( + unique int id: @typeexpr, + int kind: int ref, + int parent: @typeexpr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref +); + +case @typeexpr.kind of + 0 = @localtypeaccess +| 1 = @typedecl +| 2 = @keywordtypeexpr +| 3 = @stringliteraltypeexpr +| 4 = @numberliteraltypeexpr +| 5 = @booleanliteraltypeexpr +| 6 = @arraytypeexpr +| 7 = @uniontypeexpr +| 8 = @indexedaccesstypeexpr +| 9 = @intersectiontypeexpr +| 10 = @parenthesizedtypeexpr +| 11 = @tupletypeexpr +| 12 = @keyoftypeexpr +| 13 = @qualifiedtypeaccess +| 14 = @generictypeexpr +| 15 = @typelabel +| 16 = @typeoftypeexpr +| 17 = @localvartypeaccess +| 18 = @qualifiedvartypeaccess +| 19 = @thisvartypeaccess +| 20 = @istypeexpr +| 21 = @interfacetypeexpr +| 22 = @typeparameter +| 23 = @plainfunctiontypeexpr +| 24 = @constructortypeexpr +| 25 = @localnamespaceaccess +| 26 = @qualifiednamespaceaccess +| 27 = @mappedtypeexpr +| 28 = @conditionaltypeexpr +| 29 = @infertypeexpr +| 30 = @importtypeaccess +| 31 = @importnamespaceaccess +| 32 = @importvartypeaccess +| 33 = @optionaltypeexpr +| 34 = @resttypeexpr +; + +@typeref = @typeaccess | @typedecl; +@typeidentifier = @typedecl | @localtypeaccess | @typelabel | @localvartypeaccess | @localnamespaceaccess; +@typeexpr_parent = @expr | @stmt | @property | @typeexpr; +@literaltypeexpr = @stringliteraltypeexpr | @numberliteraltypeexpr | @booleanliteraltypeexpr; +@typeaccess = @localtypeaccess | @qualifiedtypeaccess | @importtypeaccess; +@vartypeaccess = @localvartypeaccess | @qualifiedvartypeaccess | @thisvartypeaccess | @importvartypeaccess; +@namespaceaccess = @localnamespaceaccess | @qualifiednamespaceaccess | @importnamespaceaccess; +@importtypeexpr = @importtypeaccess | @importnamespaceaccess | @importvartypeaccess; + +@functiontypeexpr = @plainfunctiontypeexpr | @constructortypeexpr; + +// types +types ( + unique int id: @type, + int kind: int ref, + varchar(900) tostring: string ref +); + +#keyset[parent, idx] +type_child ( + int child: @type ref, + int parent: @type ref, + int idx: int ref +); + +case @type.kind of + 0 = @anytype +| 1 = @stringtype +| 2 = @numbertype +| 3 = @uniontype +| 4 = @truetype +| 5 = @falsetype +| 6 = @typereference +| 7 = @objecttype +| 8 = @canonicaltypevariabletype +| 9 = @typeoftype +| 10 = @voidtype +| 11 = @undefinedtype +| 12 = @nulltype +| 13 = @nevertype +| 14 = @plainsymboltype +| 15 = @uniquesymboltype +| 16 = @objectkeywordtype +| 17 = @intersectiontype +| 18 = @tupletype +| 19 = @lexicaltypevariabletype +| 20 = @thistype +| 21 = @numberliteraltype +| 22 = @stringliteraltype +| 23 = @unknowntype +; + +@booleanliteraltype = @truetype | @falsetype; +@symboltype = @plainsymboltype | @uniquesymboltype; +@unionorintersectiontype = @uniontype | @intersectiontype; +@typevariabletype = @canonicaltypevariabletype | @lexicaltypevariabletype; + +@typed_ast_node = @expr | @typeexpr | @function; +ast_node_type( + unique int node: @typed_ast_node ref, + int typ: @type ref); + +invoke_expr_signature( + unique int node: @invokeexpr ref, + int sig: @signature_type ref +); + +invoke_expr_overload_index( + unique int node: @invokeexpr ref, + int index: int ref +); + +symbols ( + unique int id: @symbol, + int kind: int ref, + varchar(900) name: string ref +); + +symbol_parent ( + unique int symbol: @symbol ref, + int parent: @symbol ref +); + +symbol_module ( + int symbol: @symbol ref, + varchar(900) moduleName: string ref +); + +symbol_global ( + int symbol: @symbol ref, + varchar(900) globalName: string ref +); + +case @symbol.kind of + 0 = @root_symbol +| 1 = @member_symbol +| 2 = @other_symbol +; + +@type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype; +@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr; + +ast_node_symbol( + unique int node: @ast_node_with_symbol ref, + int symbol: @symbol ref); + +type_symbol( + unique int typ: @type_with_symbol ref, + int symbol: @symbol ref); + +#keyset[typ, name] +type_property( + int typ: @type ref, + varchar(900) name: string ref, + int propertyType: @type ref); + +@literaltype = @stringliteraltype | @numberliteraltype | @booleanliteraltype; +@type_with_literal_value = @stringliteraltype | @numberliteraltype; +type_literal_value( + unique int typ: @type_with_literal_value ref, + varchar(900) value: string ref); + +signature_types ( + unique int id: @signature_type, + int kind: int ref, + varchar(900) tostring: string ref, + int type_parameters: int ref, + int required_params: int ref +); + +case @signature_type.kind of + 0 = @function_signature_type +| 1 = @constructor_signature_type +; + +#keyset[typ, kind, index] +type_contains_signature ( + int typ: @type ref, + int kind: int ref, // constructor/call/index + int index: int ref, // ordering of overloaded signatures + int sig: @signature_type ref +); + +#keyset[parent, index] +signature_contains_type ( + int child: @type ref, + int parent: @signature_type ref, + int index: int ref +); + +#keyset[sig, index] +signature_parameter_name ( + int sig: @signature_type ref, + int index: int ref, + varchar(900) name: string ref +); + +number_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +string_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +base_type_names( + int typeName: @symbol ref, + int baseTypeName: @symbol ref +); + +self_types( + int typeName: @symbol ref, + int selfType: @typereference ref +); + +tuple_type_min_length( + unique int typ: @type ref, + int minLength: int ref +); + +tuple_type_rest( + unique int typ: @type ref +); + +// comments +comments (unique int id: @comment, + int kind: int ref, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(900) tostring: string ref); + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment +| 2 = @doccomment +| 3 = @htmlcommentstart +| 4 = @htmlcommentend; + +@htmlcomment = @htmlcommentstart | @htmlcommentend; +@linecomment = @slashslashcomment | @htmlcomment; +@blockcomment = @slashstarcomment | @doccomment; + +// source lines +lines (unique int id: @line, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(2) terminator: string ref); +indentation (int file: @file ref, + int lineno: int ref, + varchar(1) indentChar: string ref, + int indentDepth: int ref); + +// JavaScript parse errors +jsParseErrors (unique int id: @js_parse_error, + int toplevel: @toplevel ref, + varchar(900) message: string ref, + varchar(900) line: string ref); + +// regular expressions +#keyset[parent, idx] +regexpterm (unique int id: @regexpterm, + int kind: int ref, + int parent: @regexpparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +@regexpparent = @regexpterm | @regexpliteral; + +case @regexpterm.kind of + 0 = @regexp_alt +| 1 = @regexp_seq +| 2 = @regexp_caret +| 3 = @regexp_dollar +| 4 = @regexp_wordboundary +| 5 = @regexp_nonwordboundary +| 6 = @regexp_positive_lookahead +| 7 = @regexp_negative_lookahead +| 8 = @regexp_star +| 9 = @regexp_plus +| 10 = @regexp_opt +| 11 = @regexp_range +| 12 = @regexp_dot +| 13 = @regexp_group +| 14 = @regexp_normal_char +| 15 = @regexp_hex_escape +| 16 = @regexp_unicode_escape +| 17 = @regexp_dec_escape +| 18 = @regexp_oct_escape +| 19 = @regexp_ctrl_escape +| 20 = @regexp_char_class_escape +| 21 = @regexp_id_escape +| 22 = @regexp_backref +| 23 = @regexp_char_class +| 24 = @regexp_char_range +| 25 = @regexp_positive_lookbehind +| 26 = @regexp_negative_lookbehind +| 27 = @regexp_unicode_property_escape; + +regexpParseErrors (unique int id: @regexp_parse_error, + int regexp: @regexpterm ref, + varchar(900) message: string ref); + +@regexp_quantifier = @regexp_star | @regexp_plus | @regexp_opt | @regexp_range; +@regexp_escape = @regexp_char_escape | @regexp_char_class_escape | @regexp_unicode_property_escape; +@regexp_char_escape = @regexp_hex_escape | @regexp_unicode_escape | @regexp_dec_escape | @regexp_oct_escape | @regexp_ctrl_escape | @regexp_id_escape; +@regexp_constant = @regexp_normal_char | @regexp_char_escape; +@regexp_lookahead = @regexp_positive_lookahead | @regexp_negative_lookahead; +@regexp_lookbehind = @regexp_positive_lookbehind | @regexp_negative_lookbehind; +@regexp_subpattern = @regexp_lookahead | @regexp_lookbehind; + +isGreedy (int id: @regexp_quantifier ref); +rangeQuantifierLowerBound (unique int id: @regexp_range ref, int lo: int ref); +rangeQuantifierUpperBound (unique int id: @regexp_range ref, int hi: int ref); +isCapture (unique int id: @regexp_group ref, int number: int ref); +isNamedCapture (unique int id: @regexp_group ref, string name: string ref); +isInverted (int id: @regexp_char_class ref); +regexpConstValue (unique int id: @regexp_constant ref, varchar(1) value: string ref); +charClassEscape (unique int id: @regexp_char_class_escape ref, varchar(1) value: string ref); +backref (unique int id: @regexp_backref ref, int value: int ref); +namedBackref (unique int id: @regexp_backref ref, string name: string ref); +unicodePropertyEscapeName (unique int id: @regexp_unicode_property_escape ref, string name: string ref); +unicodePropertyEscapeValue (unique int id: @regexp_unicode_property_escape ref, string value: string ref); + +// tokens +#keyset[toplevel, idx] +tokeninfo (unique int id: @token, + int kind: int ref, + int toplevel: @toplevel ref, + int idx: int ref, + varchar(900) value: string ref); + +case @token.kind of + 0 = @token_eof +| 1 = @token_null_literal +| 2 = @token_boolean_literal +| 3 = @token_numeric_literal +| 4 = @token_string_literal +| 5 = @token_regular_expression +| 6 = @token_identifier +| 7 = @token_keyword +| 8 = @token_punctuator; + +// associate comments with the token immediately following them (which may be EOF) +next_token (int comment: @comment ref, int token: @token ref); + +// JSON +#keyset[parent, idx] +json (unique int id: @json_value, + int kind: int ref, + int parent: @json_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +json_literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @json_value ref); + +json_properties (int obj: @json_object ref, + varchar(900) property: string ref, + int value: @json_value ref); + +json_errors (unique int id: @json_parse_error, + varchar(900) message: string ref); + +case @json_value.kind of + 0 = @json_null +| 1 = @json_boolean +| 2 = @json_number +| 3 = @json_string +| 4 = @json_array +| 5 = @json_object; + +@json_parent = @json_object | @json_array | @file; + +// locations +@ast_node = @toplevel | @stmt | @expr | @property | @typeexpr; + +@locatable = @file + | @ast_node + | @comment + | @line + | @js_parse_error | @regexp_parse_error | @json_parse_error + | @regexpterm + | @json_value + | @token + | @cfg_node + | @jsdoc | @jsdoc_type_expr | @jsdoc_tag + | @yaml_node | @yaml_error + | @xmllocatable; + +hasLocation (unique int locatable: @locatable ref, + int location: @location ref); + +// CFG +entry_cfg_node (unique int id: @entry_node, int container: @stmt_container ref); +exit_cfg_node (unique int id: @exit_node, int container: @stmt_container ref); +guard_node (unique int id: @guard_node, int kind: int ref, int test: @expr ref); +case @guard_node.kind of + 0 = @falsy_guard +| 1 = @truthy_guard; +@condition_guard = @falsy_guard | @truthy_guard; + +@synthetic_cfg_node = @entry_node | @exit_node | @guard_node; +@cfg_node = @synthetic_cfg_node | @exprparent; + +successor (int pred: @cfg_node ref, int succ: @cfg_node ref); + +// JSDoc comments +jsdoc (unique int id: @jsdoc, varchar(900) description: string ref, int comment: @comment ref); +#keyset[parent, idx] +jsdoc_tags (unique int id: @jsdoc_tag, varchar(900) title: string ref, + int parent: @jsdoc ref, int idx: int ref, varchar(900) tostring: string ref); +jsdoc_tag_descriptions (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); +jsdoc_tag_names (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); + +#keyset[parent, idx] +jsdoc_type_exprs (unique int id: @jsdoc_type_expr, + int kind: int ref, + int parent: @jsdoc_type_expr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); +case @jsdoc_type_expr.kind of + 0 = @jsdoc_any_type_expr +| 1 = @jsdoc_null_type_expr +| 2 = @jsdoc_undefined_type_expr +| 3 = @jsdoc_unknown_type_expr +| 4 = @jsdoc_void_type_expr +| 5 = @jsdoc_named_type_expr +| 6 = @jsdoc_applied_type_expr +| 7 = @jsdoc_nullable_type_expr +| 8 = @jsdoc_non_nullable_type_expr +| 9 = @jsdoc_record_type_expr +| 10 = @jsdoc_array_type_expr +| 11 = @jsdoc_union_type_expr +| 12 = @jsdoc_function_type_expr +| 13 = @jsdoc_optional_type_expr +| 14 = @jsdoc_rest_type_expr +; + +#keyset[id, idx] +jsdoc_record_field_name (int id: @jsdoc_record_type_expr ref, int idx: int ref, varchar(900) name: string ref); +jsdoc_prefix_qualifier (int id: @jsdoc_type_expr ref); +jsdoc_has_new_parameter (int fn: @jsdoc_function_type_expr ref); + +@jsdoc_type_expr_parent = @jsdoc_type_expr | @jsdoc_tag; + +jsdoc_errors (unique int id: @jsdoc_error, int tag: @jsdoc_tag ref, varchar(900) message: string ref, varchar(900) tostring: string ref); + +// YAML +#keyset[parent, idx] +yaml (unique int id: @yaml_node, + int kind: int ref, + int parent: @yaml_node_parent ref, + int idx: int ref, + varchar(900) tag: string ref, + varchar(900) tostring: string ref); + +case @yaml_node.kind of + 0 = @yaml_scalar_node +| 1 = @yaml_mapping_node +| 2 = @yaml_sequence_node +| 3 = @yaml_alias_node +; + +@yaml_collection_node = @yaml_mapping_node | @yaml_sequence_node; + +@yaml_node_parent = @yaml_collection_node | @file; + +yaml_anchors (unique int node: @yaml_node ref, + varchar(900) anchor: string ref); + +yaml_aliases (unique int alias: @yaml_alias_node ref, + varchar(900) target: string ref); + +yaml_scalars (unique int scalar: @yaml_scalar_node ref, + int style: int ref, + varchar(900) value: string ref); + +yaml_errors (unique int id: @yaml_error, + varchar(900) message: string ref); + +/* XML Files */ + +xmlEncoding( + unique int id: @file ref, + varchar(900) encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + varchar(900) root: string ref, + varchar(900) publicId: string ref, + varchar(900) systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + varchar(900) name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + varchar(900) name: string ref, + varchar(3600) value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + varchar(900) prefixName: string ref, + varchar(900) URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +@dataflownode = @expr | @functiondeclstmt | @classdeclstmt | @namespacedeclaration | @enumdeclaration | @property; + +@optionalchainable = @callexpr | @propaccess; + +isOptionalChaining(int id: @optionalchainable ref); + +/* Last updated 2018/10/22. */ diff --git a/javascript/upgrades/6486c78671c40e4dc07932d806366f09051bb399/upgrade.properties b/javascript/upgrades/6486c78671c40e4dc07932d806366f09051bb399/upgrade.properties new file mode 100644 index 000000000000..f0904d194e19 --- /dev/null +++ b/javascript/upgrades/6486c78671c40e4dc07932d806366f09051bb399/upgrade.properties @@ -0,0 +1,2 @@ +description: 'introduce @isOptionalChaining' +compatibility: backwards \ No newline at end of file diff --git a/python/ql/src/.project b/python/ql/src/.project new file mode 100644 index 000000000000..1f9146259c20 --- /dev/null +++ b/python/ql/src/.project @@ -0,0 +1,18 @@ + + + semmlecode-python-queries + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + com.semmle.plugin.qdt.core.qlnature + + diff --git a/python/ql/src/.qlpath b/python/ql/src/.qlpath new file mode 100644 index 000000000000..7c2c1b3b672c --- /dev/null +++ b/python/ql/src/.qlpath @@ -0,0 +1,6 @@ + + + + /semmlecode-python-queries/semmlecode.python.dbscheme + python + diff --git a/python/ql/src/Classes/ConflictingAttributesInBaseClasses.py b/python/ql/src/Classes/ConflictingAttributesInBaseClasses.py new file mode 100644 index 000000000000..295e06e86e91 --- /dev/null +++ b/python/ql/src/Classes/ConflictingAttributesInBaseClasses.py @@ -0,0 +1,18 @@ + +class TCPServer(object): + + def process_request(self, request, client_address): + self.do_work(request, client_address) + self.shutdown_request(request) + + +class ThreadingMixIn: + """Mix-in class to handle each request in a new thread.""" + + def process_request(self, request, client_address): + """Start a new thread to process the request.""" + t = threading.Thread(target = self.do_work, args = (request, client_address)) + t.daemon = self.daemon_threads + t.start() + +class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass diff --git a/python/ql/src/Classes/ConflictingAttributesInBaseClasses.qhelp b/python/ql/src/Classes/ConflictingAttributesInBaseClasses.qhelp new file mode 100644 index 000000000000..315ffae585f4 --- /dev/null +++ b/python/ql/src/Classes/ConflictingAttributesInBaseClasses.qhelp @@ -0,0 +1,59 @@ + + + + +

    +When a class subclasses multiple base classes, attribute lookup is performed from left to right amongst the base classes. +This form of attribute lookup is called "method resolution order" and is a solution to the +diamond inheritance problem where several base classes +override a method in a shared superclass. +

    +

    +Unfortunately, this means that if more than one base class defines the same attribute, the leftmost base class will effectively override +the attribute of the rightmost base class, even though the leftmost base class is not a subclass of the rightmost base class. +Unless the methods in question are designed for inheritance, using super, then this implicit overriding may not be the desired behavior. +Even if it is the desired behavior it makes the code hard to understand and maintain. +

    + +
    + +

    There are a number of ways that might be used to address this issue: +

      +
    • Override the attribute in the subclass to implement the correct behavior.
    • +
    • Modify the class hierarchy and move equivalent or redundant methods to a common super class.
    • +
    • Modify the method hierarchy, breaking up complex methods into constituent parts.
    • +
    • Use delegation rather than inheritance.
    • +
    + +
    + + +

    +In this example the class ThreadingTCPServer inherits from ThreadingMixIn and from TCPServer. +However, both these classes implement process_request which means that ThreadingTCPServer will inherit +process_request from ThreadingMixIn. Consequently, the implementation of process_request in TCPServer +will be ignored, which may not be the correct behavior. +

    + + +

    +This can be fixed either by overriding the method, as shown in class ThreadingTCPServerOverriding +or by ensuring that the +functionality provided by the two base classes does not overlap, as shown in class ThreadingTCPServerChangedHierarchy. +

    + + + +
    + + +
  • Python Language Reference: Data model.
  • +
  • Python releases: The Python 2.3 Method Resolution Order.
  • +
  • Wikipedia: C3 linearization.
  • +
  • Wikipedia: Composition over inheritance.
  • + + +
    +
    diff --git a/python/ql/src/Classes/ConflictingAttributesInBaseClasses.ql b/python/ql/src/Classes/ConflictingAttributesInBaseClasses.ql new file mode 100644 index 000000000000..44be7ac9157a --- /dev/null +++ b/python/ql/src/Classes/ConflictingAttributesInBaseClasses.ql @@ -0,0 +1,57 @@ +/** + * @name Conflicting attributes in base classes + * @description When a class subclasses multiple base classes and more than one base class defines the same attribute, attribute overriding may result in unexpected behavior by instances of this class. + * @kind problem + * @tags reliability + * maintainability + * modularity + * @problem.severity warning + * @sub-severity low + * @precision high + * @id py/conflicting-attributes + */ + +import python + +predicate does_nothing(PyFunctionObject f) { + not exists(Stmt s | s.getScope() = f.getFunction() | + not s instanceof Pass and not ((ExprStmt)s).getValue() = f.getFunction().getDocString() + ) +} + +/* If a method performs a super() call then it is OK as the 'overridden' method will get called */ +predicate calls_super(FunctionObject f) { + exists(Call sup, Call meth, Attribute attr, GlobalVariable v | + meth.getScope() = f.getFunction() and + meth.getFunc() = attr and + attr.getObject() = sup and + attr.getName() = f.getName() and + sup.getFunc() = v.getAnAccess() and + v.getId() = "super" + ) +} + +/** Holds if the given name is white-listed for some reason */ +predicate whitelisted(string name) { + /* The standard library specifically recommends this :( + * See https://docs.python.org/3/library/socketserver.html#asynchronous-mixins */ + name = "process_request" +} + +from ClassObject c, ClassObject b1, ClassObject b2, string name, +int i1, int i2, Object o1, Object o2 +where c.getBaseType(i1) = b1 and +c.getBaseType(i2) = b2 and +i1 < i2 and o1 != o2 and +o1 = b1.lookupAttribute(name) and +o2 = b2.lookupAttribute(name) and +not name.matches("\\_\\_%\\_\\_") and +not calls_super(o1) and +not does_nothing(o2) and +not whitelisted(name) and +not o1.overrides(o2) and +not o2.overrides(o1) and +not c.declaresAttribute(name) + +select c, "Base classes have conflicting values for attribute '" + name + "': $@ and $@.", o1, o1.toString(), o2, o2.toString() + diff --git a/python/ql/src/Classes/ConflictingAttributesInBaseClasses_Fixed.py b/python/ql/src/Classes/ConflictingAttributesInBaseClasses_Fixed.py new file mode 100644 index 000000000000..c106064f2a87 --- /dev/null +++ b/python/ql/src/Classes/ConflictingAttributesInBaseClasses_Fixed.py @@ -0,0 +1,24 @@ + +#Fixed by overriding. This does not change behavior, but makes it explicit and comprehensible. +class ThreadingTCPServerOverriding(ThreadingMixIn, TCPServer): + + def process_request(self, request, client_address): + #process_request forwards to do_work, so it is OK to call ThreadingMixIn.process_request directly + ThreadingMixIn.process_request(self, request, client_address) + + +#Fixed by separating threading functionality from request handling. +class ThreadingMixIn: + """Mix-in class to help with threads.""" + + def do_job_in_thread(self, job, args): + """Start a new thread to do the job""" + t = threading.Thread(target = job, args = args) + t.start() + +class ThreadingTCPServerChangedHierarchy(ThreadingMixIn, TCPServer): + + def process_request(self, request, client_address): + """Start a new thread to process the request.""" + self.do_job_in_thread(self.do_work, (request, client_address)) + diff --git a/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.py b/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.py new file mode 100644 index 000000000000..e8ae058309f4 --- /dev/null +++ b/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.py @@ -0,0 +1,40 @@ +class Point(object): + + def __init__(self, x, y): + self._x = x + self._y = y + + def __repr__(self): + return 'Point(%r, %r)' % (self._x, self._y) + + def __eq__(self, other): + if not isinstance(other, Point): + return False + return self._x == other._x and self._y == other._y + +class ColorPoint(Point): + + def __init__(self, x, y, color): + Point.__init__(self, x, y) + self._color = color + + def __repr__(self): + return 'ColorPoint(%r, %r)' % (self._x, self._y, self._color) + +#ColorPoint(0, 0, Red) == ColorPoint(0, 0, Green) should be False, but is True. + +#Fixed version +class ColorPoint(Point): + + def __init__(self, x, y, color): + Point.__init__(self, x, y) + self._color = color + + def __repr__(self): + return 'ColorPoint(%r, %r)' % (self._x, self._y, self._color) + + def __eq__(self, other): + if not isinstance(other, ColorPoint): + return False + return Point.__eq__(self, other) and self._color = other._color + diff --git a/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.qhelp b/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.qhelp new file mode 100644 index 000000000000..0260c6456e69 --- /dev/null +++ b/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.qhelp @@ -0,0 +1,37 @@ + + + + +

    A class that defines attributes that are not present in its superclasses +may need to override the __eq__() method (__ne__() +should also be defined).

    + +

    Adding additional attributes without overriding __eq__() means +that the additional attributes will not be accounted for in equality tests.

    + + +
    + + +

    Override the __eq__ method.

    + + +
    + +

    In the following example the ColorPoint +class subclasses the Point class and adds a new attribute, +but does not override the __eq__ method. +

    + + + + +
    + + +
  • Peter Grogono, Philip Santas: Equality in Object Oriented Languages
  • + +
    +
    diff --git a/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.ql b/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.ql new file mode 100644 index 000000000000..5b80f2fd7bfe --- /dev/null +++ b/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.ql @@ -0,0 +1,52 @@ +/** + * @name __eq__ not overridden when adding attributes + * @description When adding new attributes to instances of a class, equality for that class needs to be defined. + * @kind problem + * @tags reliability + * correctness + * @problem.severity warning + * @sub-severity high + * @precision high + * @id py/missing-equals + */ + +import python +import semmle.python.SelfAttribute +import Equality + +predicate class_stores_to_attribute(ClassObject cls, SelfAttributeStore store, string name) { + exists(FunctionObject f | f = cls.declaredAttribute(_) and store.getScope() = f.getFunction() and store.getName() = name) and + /* Exclude classes used as metaclasses */ + not cls.getASuperType() = theTypeType() +} + +predicate should_override_eq(ClassObject cls, Object base_eq) { + not cls.declaresAttribute("__eq__") and + exists(ClassObject sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq | + not exists(GenericEqMethod eq | eq.getScope() = sup.getPyClass()) and + not exists(IdentityEqMethod eq | eq.getScope() = sup.getPyClass()) and + not base_eq.(FunctionObject).getFunction() instanceof IdentityEqMethod and + not base_eq = theObjectType().declaredAttribute("__eq__") + ) +} + +/** Does the non-overridden __eq__ method access the attribute, + * which implies that the __eq__ method does not need to be overridden. + */ +predicate superclassEqExpectsAttribute(ClassObject cls, PyFunctionObject base_eq, string attrname) { + not cls.declaresAttribute("__eq__") and + exists(ClassObject sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq | + exists(SelfAttributeRead store | + store.getName() = attrname | + store.getScope() = base_eq.getFunction() + ) + ) +} + +from ClassObject cls, SelfAttributeStore store, Object base_eq +where class_stores_to_attribute(cls, store, _) and should_override_eq(cls, base_eq) and +/* Don't report overridden unittest.TestCase. -- TestCase overrides __eq__, but subclasses do not really need to. */ +not cls.getASuperType().getName() = "TestCase" and +not superclassEqExpectsAttribute(cls, base_eq, store.getName()) + +select cls, "The class '" + cls.getName() + "' does not override $@, but adds the new attribute $@.", base_eq, "'__eq__'", store, store.getName() diff --git a/python/ql/src/Classes/Equality.qll b/python/ql/src/Classes/Equality.qll new file mode 100644 index 000000000000..5f7648fafc49 --- /dev/null +++ b/python/ql/src/Classes/Equality.qll @@ -0,0 +1,71 @@ +import python + + +private Attribute dictAccess(LocalVariable var) { + result.getName() = "__dict__" and + result.getObject() = var.getAnAccess() +} + +private Call getattr(LocalVariable obj, LocalVariable attr) { + result.getFunc().(Name).getId() = "getattr" and + result.getArg(0) = obj.getAnAccess() and + result.getArg(1) = attr.getAnAccess() +} + +/** A generic equality method that compares all attributes in its dict, + * or compares attributes using `getattr`. */ +class GenericEqMethod extends Function { + + GenericEqMethod() { + this.getName() = "__eq__" and + exists(LocalVariable self, LocalVariable other | + self.getAnAccess() = this.getArg(0) and self.getId() = "self" and + other.getAnAccess() = this.getArg(1) and + exists(Compare eq | + eq.getOp(0) instanceof Eq or + eq.getOp(0) instanceof NotEq | + // `self.__dict__ == other.__dict__` + eq.getAChildNode() = dictAccess(self) and + eq.getAChildNode() = dictAccess(other) + or + // `getattr(self, var) == getattr(other, var)` + exists(Variable var | + eq.getAChildNode() = getattr(self, var) and + eq.getAChildNode() = getattr(other, var) + ) + ) + ) + } +} + +/** An __eq__ method that just does self is other */ +class IdentityEqMethod extends Function { + + IdentityEqMethod() { + this.getName() = "__eq__" and + exists(LocalVariable self, LocalVariable other | + self.getAnAccess() = this.getArg(0) and self.getId() = "self" and + other.getAnAccess() = this.getArg(1) and + exists(Compare eq | eq.getOp(0) instanceof Is | + eq.getAChildNode() = self.getAnAccess() and + eq.getAChildNode() = other.getAnAccess() + ) + ) + } + +} + +/** An (in)equality method that delegates to its complement */ +class DelegatingEqualityMethod extends Function { + + DelegatingEqualityMethod() { + exists(Return ret, UnaryExpr not_, Compare comp, Cmpop op, Parameter p0, Parameter p1 | + ret.getScope() = this and + ret.getValue() = not_ and + not_.getOp() instanceof Not and not_.getOperand() = comp and + comp.compares(p0.getVariable().getAnAccess(), op, p1.getVariable().getAnAccess()) | + this.getName() = "__eq__" and op instanceof NotEq or + this.getName() = "__ne__" and op instanceof Eq + ) + } +} diff --git a/python/ql/src/Classes/EqualsOrHash.py b/python/ql/src/Classes/EqualsOrHash.py new file mode 100644 index 000000000000..e89c75b30ada --- /dev/null +++ b/python/ql/src/Classes/EqualsOrHash.py @@ -0,0 +1,52 @@ +# Incorrect: equality method defined but class contains no hash method +class Point(object): + + def __init__(self, x, y): + self._x = x + self._y = y + + def __repr__(self): + return 'Point(%r, %r)' % (self._x, self._y) + + def __eq__(self, other): + if not isinstance(other, Point): + return False + return self._x == other._x and self._y == other._y + + +# Improved: equality and hash method defined (inequality method still missing) +class PointUpdated(object): + + def __init__(self, x, y): + self._x = x + self._y = y + + def __repr__(self): + return 'Point(%r, %r)' % (self._x, self._y) + + def __eq__(self, other): + if not isinstance(other, Point): + return False + return self._x == other._x and self._y == other._y + + def __hash__(self): + return hash(self._x) ^ hash(self._y) + +# Improved: equality method defined and class instances made unhashable +class UnhashablePoint(object): + + def __init__(self, x, y): + self._x = x + self._y = y + + def __repr__(self): + return 'Point(%r, %r)' % (self._x, self._y) + + def __eq__(self, other): + if not isinstance(other, Point): + return False + return self._x == other._x and self._y == other._y + + #Tell the interpreter that instances of this class cannot be hashed + __hash__ = None + diff --git a/python/ql/src/Classes/EqualsOrHash.qhelp b/python/ql/src/Classes/EqualsOrHash.qhelp new file mode 100644 index 000000000000..28579a095f70 --- /dev/null +++ b/python/ql/src/Classes/EqualsOrHash.qhelp @@ -0,0 +1,46 @@ + + + + +

    In order to conform to the object model, classes that define their own equality method should also +define their own hash method, or be unhashable. If the hash method is not defined then the hash of the +super class is used. This is unlikely to result in the expected behavior.

    + +

    A class can be made unhashable by setting its __hash__ attribute to None.

    + +

    In Python 3, if you define a class-level equality method and omit a __hash__ method +then the class is automatically marked as unhashable.

    + +
    + + +

    When you define an __eq__ method for a class, remember to implement a __hash__ method or set +__hash__ = None.

    + +
    + +

    In the following example the Point class defines an equality method but +no hash method. If hash is called on this class then the hash method defined for object +is used. This is unlikely to give the required behavior. The PointUpdated class +is better as it defines both an equality and a hash method. +If Point was not to be used in dicts or sets, then it could be defined as +UnhashablePoint below. +

    +

    +To comply fully with the object model this class should also define an inequality method (identified +by a separate rule).

    + + + +
    + + + +
  • Python Language Reference: object.__hash__.
  • +
  • Python Glossary: hashable.
  • + + +
    +
    diff --git a/python/ql/src/Classes/EqualsOrHash.ql b/python/ql/src/Classes/EqualsOrHash.ql new file mode 100644 index 000000000000..a0ff96c6eae1 --- /dev/null +++ b/python/ql/src/Classes/EqualsOrHash.ql @@ -0,0 +1,46 @@ +/** + * @name Inconsistent equality and hashing + * @description Defining equality for a class without also defining hashability (or vice-versa) violates the object model. + * @kind problem + * @tags reliability + * correctness + * external/cwe/cwe-581 + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/equals-hash-mismatch + */ + +import python + +FunctionObject defines_equality(ClassObject c, string name) { + (name = "__eq__" or major_version() = 2 and name = "__cmp__") + and + result = c.declaredAttribute(name) +} + +FunctionObject implemented_method(ClassObject c, string name) { + result = defines_equality(c, name) + or + result = c.declaredAttribute("__hash__") and name = "__hash__" +} + +string unimplemented_method(ClassObject c) { + not exists(defines_equality(c, _)) and + (result = "__eq__" and major_version() = 3 or major_version() = 2 and result = "__eq__ or __cmp__") + or + /* Python 3 automatically makes classes unhashable if __eq__ is defined, but __hash__ is not */ + not c.declaresAttribute(result) and result = "__hash__" and major_version() = 2 +} + +predicate violates_hash_contract(ClassObject c, string present, string missing, Object method) { + not c.unhashable() and + missing = unimplemented_method(c) and + method = implemented_method(c, present) and + not c.unknowableAttributes() +} + +from ClassObject c, string present, string missing, FunctionObject method +where violates_hash_contract(c, present, missing, method) and +exists(c.getPyClass()) // Suppress results that aren't from source +select method, "Class $@ implements " + present + " but does not define " + missing + ".", c, c.getName() diff --git a/python/ql/src/Classes/EqualsOrNotEquals.py b/python/ql/src/Classes/EqualsOrNotEquals.py new file mode 100644 index 000000000000..7e1ece7685c5 --- /dev/null +++ b/python/ql/src/Classes/EqualsOrNotEquals.py @@ -0,0 +1,32 @@ +class PointOriginal(object): + + def __init__(self, x, y): + self._x, x + self._y = y + + def __repr__(self): + return 'Point(%r, %r)' % (self._x, self._y) + + def __eq__(self, other): # Incorrect: equality is defined but inequality is not + if not isinstance(other, Point): + return False + return self._x == other._x and self._y == other._y + + +class PointUpdated(object): + + def __init__(self, x, y): + self._x, x + self._y = y + + def __repr__(self): + return 'Point(%r, %r)' % (self._x, self._y) + + def __eq__(self, other): + if not isinstance(other, Point): + return False + return self._x == other._x and self._y == other._y + + def __ne__(self, other): # Improved: equality and inequality method defined (hash method still missing) + return not self == other + diff --git a/python/ql/src/Classes/EqualsOrNotEquals.qhelp b/python/ql/src/Classes/EqualsOrNotEquals.qhelp new file mode 100644 index 000000000000..c49f3d2529ed --- /dev/null +++ b/python/ql/src/Classes/EqualsOrNotEquals.qhelp @@ -0,0 +1,37 @@ + + + + +

    In order to conform to the object model, classes should define either no equality methods, or both +an equality and an inequality method. If only one of __eq__ or __ne__ is +defined then the method from the super class is used. This is unlikely to result in the expected +behavior.

    + +
    + + +

    When you define an equality or an inequality method for a class, remember to implement both an +__eq__ method and an __ne__ method.

    + +
    + +

    In the following example the PointOriginal class defines an equality method but +no inequality method. If this class is tested for inequality then a type error will be raised. The +PointUpdated class is better as it defines both an equality and an inequality method. To +comply fully with the object model this class should also define a hash method (identified by +a separate rule).

    + + + +
    + + + +
  • Python Language Reference: object.__ne__, +Comparisons.
  • + + +
    +
    diff --git a/python/ql/src/Classes/EqualsOrNotEquals.ql b/python/ql/src/Classes/EqualsOrNotEquals.ql new file mode 100644 index 000000000000..9674030a64cc --- /dev/null +++ b/python/ql/src/Classes/EqualsOrNotEquals.ql @@ -0,0 +1,50 @@ +/** + * @name Inconsistent equality and inequality + * @description Defining only an equality method or an inequality method for a class violates the object model. + * @kind problem + * @tags reliability + * correctness + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/inconsistent-equality + */ + +import python +import Equality + +string equals_or_ne() { + result = "__eq__" or result = "__ne__" +} + +predicate total_ordering(Class cls) { + exists(Attribute a | a = cls.getADecorator() | + a.getName() = "total_ordering") + or + exists(Name n | n = cls.getADecorator() | + n.getId() = "total_ordering") +} + +FunctionObject implemented_method(ClassObject c, string name) { + result = c.declaredAttribute(name) and name = equals_or_ne() +} + +string unimplemented_method(ClassObject c) { + not c.declaresAttribute(result) and result = equals_or_ne() +} + +predicate violates_equality_contract(ClassObject c, string present, string missing, FunctionObject method) { + missing = unimplemented_method(c) and + method = implemented_method(c, present) and + not c.unknowableAttributes() and + not total_ordering(c.getPyClass()) and + /* Python 3 automatically implements __ne__ if __eq__ is defined, but not vice-versa */ + not (major_version() = 3 and present = "__eq__" and missing = "__ne__") and + not method.getFunction() instanceof DelegatingEqualityMethod and + not c.lookupAttribute(missing).(FunctionObject).getFunction() instanceof DelegatingEqualityMethod +} + +from ClassObject c, string present, string missing, FunctionObject method +where violates_equality_contract(c, present, missing, method) + +select method, "Class $@ implements " + present + " but does not implement " + missing + ".", c, c.getName() diff --git a/python/ql/src/Classes/IncompleteOrdering.py b/python/ql/src/Classes/IncompleteOrdering.py new file mode 100644 index 000000000000..78b306880b03 --- /dev/null +++ b/python/ql/src/Classes/IncompleteOrdering.py @@ -0,0 +1,6 @@ +class IncompleteOrdering(object): + def __init__(self, i): + self.i = i + + def __lt__(self, other): + return self.i < other.i \ No newline at end of file diff --git a/python/ql/src/Classes/IncompleteOrdering.qhelp b/python/ql/src/Classes/IncompleteOrdering.qhelp new file mode 100644 index 000000000000..7983985ccee0 --- /dev/null +++ b/python/ql/src/Classes/IncompleteOrdering.qhelp @@ -0,0 +1,35 @@ + + + +

    A class that implements an ordering operator +(__lt__, __gt__, __le__ or __ge__) should implement +all four in order that ordering between two objects is consistent and obeys the usual mathematical rules. +If the ordering is inconsistent with default equality, then __eq__ and __ne__ +should also be implemented. +

    + +
    + +

    Ensure that all four ordering comparisons are implemented as well as __eq__ and +__ne__ if required.

    + +

    It is not necessary to manually implement all four comparisons, +the functools.total_ordering class decorator can be used.

    + +
    + +

    In this example only the __lt__ operator has been implemented which could lead to +inconsistent behavior. __gt__, __le__, __ge__, and in this case, +__eq__ and __ne__ should be implemented.

    + + +
    + + +
  • Python Language Reference: Rich comparisons in Python.
  • + + +
    +
    diff --git a/python/ql/src/Classes/IncompleteOrdering.ql b/python/ql/src/Classes/IncompleteOrdering.ql new file mode 100644 index 000000000000..03c2ddf390ca --- /dev/null +++ b/python/ql/src/Classes/IncompleteOrdering.ql @@ -0,0 +1,75 @@ +/** + * @name Incomplete ordering + * @description Class defines one or more ordering method but does not define all 4 ordering comparison methods + * @kind problem + * @tags reliability + * correctness + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/incomplete-ordering + */ + +import python + +predicate total_ordering(Class cls) { + exists(Attribute a | a = cls.getADecorator() | + a.getName() = "total_ordering") + or + exists(Name n | n = cls.getADecorator() | + n.getId() = "total_ordering") +} + +string ordering_name(int n) { + result = "__lt__" and n = 1 or + result = "__le__" and n = 2 or + result = "__gt__" and n = 3 or + result = "__ge__" and n = 4 +} + +predicate overrides_ordering_method(ClassObject c, string name) { + name = ordering_name(_) and + ( + c.declaresAttribute(name) + or + exists(ClassObject sup | + sup = c.getASuperType() and not sup = theObjectType() | + sup.declaresAttribute(name) + ) + ) +} + +string unimplemented_ordering(ClassObject c, int n) { + not c = theObjectType() and + not overrides_ordering_method(c, result) and + result = ordering_name(n) +} + +string unimplemented_ordering_methods(ClassObject c, int n) { + n = 0 and result = "" and exists(unimplemented_ordering(c, _)) + or + exists(string prefix, int nm1 | + n = nm1 + 1 and prefix = unimplemented_ordering_methods(c, nm1) | + prefix = "" and result = unimplemented_ordering(c, n) + or + result = prefix and not exists(unimplemented_ordering(c, n)) and n < 5 + or + prefix != "" and result = prefix + " or " + unimplemented_ordering(c, n) + ) +} + +Object ordering_method(ClassObject c, string name) { + /* If class doesn't declare a method then don't blame this class (the superclass will be blamed). */ + name = ordering_name(_) and result = c.declaredAttribute(name) +} + +from ClassObject c, Object ordering, string name +where not c.unknowableAttributes() and +not total_ordering(c.getPyClass()) +and ordering = ordering_method(c, name) and +exists(unimplemented_ordering(c, _)) + +select c, "Class " + c.getName() + " implements $@, but does not implement " + unimplemented_ordering_methods(c, 4) + ".", +ordering, name + + diff --git a/python/ql/src/Classes/InconsistentMRO.py b/python/ql/src/Classes/InconsistentMRO.py new file mode 100644 index 000000000000..767782b25c79 --- /dev/null +++ b/python/ql/src/Classes/InconsistentMRO.py @@ -0,0 +1,6 @@ +class X(object): + def __init__(self): + print("X") +class Y(object,X): + def __init__(self): + print("Y") \ No newline at end of file diff --git a/python/ql/src/Classes/InconsistentMRO.qhelp b/python/ql/src/Classes/InconsistentMRO.qhelp new file mode 100644 index 000000000000..4c06a058a162 --- /dev/null +++ b/python/ql/src/Classes/InconsistentMRO.qhelp @@ -0,0 +1,30 @@ + + + +

    Python 2.3 introduced new-style classes (classes inheriting from object). New-style classes use +the C3 linearization method to determine a method resolution ordering (MRO) for each class. The C3 +linearization method ensures that for a class C, if a class C1 precedes a class C2 in the MRO of C +then C1 should also precede C2 in the MRO of all subclasses of C. It is possible to create a +situation where it is impossible to achieve this consistency and this will guarantee that a +TypeError will be raised at runtime.

    + +
    + +

    Use a class hierarchy that is not ambiguous.

    + +
    + +

    The MRO of class X is just X, object. The program will fail when the MRO +of class Y needs to be calculated because object precedes X in +the definition of Y but the opposite is true in the MRO of X.

    + + +
    + + +
  • Python: The Python 2.3 Method Resolution Order.
  • + +
    +
    diff --git a/python/ql/src/Classes/InconsistentMRO.ql b/python/ql/src/Classes/InconsistentMRO.ql new file mode 100644 index 000000000000..08b1016086c3 --- /dev/null +++ b/python/ql/src/Classes/InconsistentMRO.ql @@ -0,0 +1,27 @@ +/** + * @name Inconsistent method resolution order + * @description Class definition will raise a type error at runtime due to inconsistent method resolution order(MRO) + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity high + * @precision very-high + * @id py/inconsistent-mro + */ + +import python + +ClassObject left_base(ClassObject type, ClassObject base) { + exists(int i | i > 0 and type.getBaseType(i) = base and result = type.getBaseType(i-1)) +} + +predicate invalid_mro(ClassObject t, ClassObject left, ClassObject right) { + t.isNewStyle() and + left = left_base(t, right) and left = right.getAnImproperSuperType() +} + +from ClassObject t, ClassObject left, ClassObject right +where invalid_mro(t, left, right) +select t, "Construction of class " + t.getName() + " can fail due to invalid method resolution order(MRO) for bases $@ and $@.", +left, left.getName(), right, right.getName() \ No newline at end of file diff --git a/python/ql/src/Classes/InitCallsSubclassMethod.py b/python/ql/src/Classes/InitCallsSubclassMethod.py new file mode 100644 index 000000000000..6e0dedb0142d --- /dev/null +++ b/python/ql/src/Classes/InitCallsSubclassMethod.py @@ -0,0 +1,48 @@ +#Superclass __init__ calls subclass method + +class Super(object): + + def __init__(self, arg): + self._state = "Not OK" + self.set_up(arg) + self._state = "OK" + + def set_up(self, arg): + "Do some set up" + +class Sub(Super): + + def __init__(self, arg): + Super.__init__(self, arg) + self.important_state = "OK" + + def set_up(self, arg): + Super.set_up(self, arg) + "Do some more set up" # Dangerous as self._state is "Not OK" + + +#Improved version with inheritance: + +class Super(object): + + def __init__(self, arg): + self._state = "Not OK" + self.super_set_up(arg) + self._state = "OK" + + def super_set_up(self, arg): + "Do some set up" + + +class Sub(Super): + + def __init__(self, arg): + Super.__init__(self, arg) + self.sub_set_up(self, arg) + self.important_state = "OK" + + + def sub_set_up(self, arg): + "Do some more set up" + + diff --git a/python/ql/src/Classes/InitCallsSubclassMethod.qhelp b/python/ql/src/Classes/InitCallsSubclassMethod.qhelp new file mode 100644 index 000000000000..72904a0bd296 --- /dev/null +++ b/python/ql/src/Classes/InitCallsSubclassMethod.qhelp @@ -0,0 +1,42 @@ + + + +

    +When an instance of a class is initialized, the super-class state should be +fully initialized before it becomes visible to the subclass. +Calling methods of the subclass in the superclass' __init__ +method violates this important invariant. +

    + +
    + + +

    Do not use methods that are subclassed in the construction of an object. +For simpler cases move the initialization into the superclass' __init__ method, +preventing it being overridden. Additional initialization of subclass should +be done in the __init__ method of the subclass. +For more complex cases, it is advisable to use a static method or function to manage +object creation. +

    + +

    Alternatively, avoid inheritance altogether using composition instead.

    + +
    + + + + + + + +
  • CERT Secure Coding: +Rule MET05-J. Although this is a Java rule it applies to most object-oriented languages.
  • +
  • Python Standard Library: Static methods.
  • +
  • Wikipedia: Composition over inheritance.
  • + + + +
    +
    diff --git a/python/ql/src/Classes/InitCallsSubclassMethod.ql b/python/ql/src/Classes/InitCallsSubclassMethod.ql new file mode 100644 index 000000000000..5a191d861bf2 --- /dev/null +++ b/python/ql/src/Classes/InitCallsSubclassMethod.ql @@ -0,0 +1,35 @@ +/** + * @name __init__ method calls overridden method + * @description Calling a method from __init__ that is overridden by a subclass may result in a partially + * initialized instance being observed. + * @kind problem + * @tags reliability + * correctness + * @problem.severity warning + * @sub-severity low + * @precision high + * @id py/init-calls-subclass + */ + +import python + + +from ClassObject supercls, string method, Call call, + FunctionObject overriding, FunctionObject overridden + +where +exists(FunctionObject init, SelfAttribute sa | + supercls.declaredAttribute("__init__") = init and + call.getScope() = init.getFunction() and call.getFunc() = sa | + sa.getName() = method and + overridden = supercls.declaredAttribute(method) and + overriding.overrides(overridden) +) + +select call, "Call to self.$@ in __init__ method, which is overridden by $@.", + overridden, method, + overriding, overriding.descriptiveString() + + + + diff --git a/python/ql/src/Classes/MaybeUndefinedClassAttribute.py b/python/ql/src/Classes/MaybeUndefinedClassAttribute.py new file mode 100644 index 000000000000..56ef98051962 --- /dev/null +++ b/python/ql/src/Classes/MaybeUndefinedClassAttribute.py @@ -0,0 +1,25 @@ +class Spam: + + def __init__(self): + self.spam = 'spam, spam, spam' + + def set_eggs(eggs): + self.eggs = eggs + + def __str__(self): + return '%s and %s' % (self.spam, self.eggs) # Maybe uninitialized attribute 'eggs' + +#Fixed version + +class Spam: + + def __init__(self): + self.spam = 'spam, spam, spam' + self.eggs = None + + def set_eggs(eggs): + self.eggs = eggs + + def __str__(self): + return '%s and %s' % (self.spam, self.eggs) # OK + diff --git a/python/ql/src/Classes/MaybeUndefinedClassAttribute.qhelp b/python/ql/src/Classes/MaybeUndefinedClassAttribute.qhelp new file mode 100644 index 000000000000..d4be086c5359 --- /dev/null +++ b/python/ql/src/Classes/MaybeUndefinedClassAttribute.qhelp @@ -0,0 +1,32 @@ + + + + + +

    A possibly non-existent attribute of self is accessed in a method. +The attribute is set in another method of the class, but may be uninitialized if the +method that uses the attribute is called before the one that sets it. +This may result in an AttributeError at run time. +

    + +
    + + +

    Ensure that all attributes are initialized in the __init__ method.

    + + +
    + + + + + + + +
  • Python Standard Library: exception AttributeError. +
  • + +
    +
    diff --git a/python/ql/src/Classes/MaybeUndefinedClassAttribute.ql b/python/ql/src/Classes/MaybeUndefinedClassAttribute.ql new file mode 100644 index 000000000000..234cf9e2973a --- /dev/null +++ b/python/ql/src/Classes/MaybeUndefinedClassAttribute.ql @@ -0,0 +1,40 @@ +/** + * @name Maybe undefined class attribute + * @description Accessing an attribute of 'self' that is not initialized in the __init__ method may cause an AttributeError at runtime + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision low + * @id py/maybe-undefined-attribute + */ + +import python +import semmle.python.SelfAttribute + +predicate guarded_by_other_attribute(SelfAttributeRead a, CheckClass c) { + c.sometimesDefines(a.getName()) and + exists(SelfAttributeRead guard, If i | + i.contains(a) and + c.assignedInInit(guard.getName()) | + i.getTest() = guard + or + i.getTest().contains(guard) + ) +} + + +predicate maybe_undefined_class_attribute(SelfAttributeRead a, CheckClass c) { + c.sometimesDefines(a.getName()) and + not c.alwaysDefines(a.getName()) and + c.interestingUndefined(a) and + not guarded_by_other_attribute(a, c) +} + +from Attribute a, ClassObject c, SelfAttributeStore sa +where maybe_undefined_class_attribute(a, c) and +sa.getClass() = c.getPyClass() and sa.getName() = a.getName() +select a, "Attribute '" + a.getName() + +"' is not defined in the class body nor in the __init__() method, but it is defined $@", sa, "here" + diff --git a/python/ql/src/Classes/MethodCallOrder.qll b/python/ql/src/Classes/MethodCallOrder.qll new file mode 100644 index 000000000000..fe6ef07266a7 --- /dev/null +++ b/python/ql/src/Classes/MethodCallOrder.qll @@ -0,0 +1,67 @@ +import python + +// Helper predicates for multiple call to __init__/__del__ queries. + +pragma [noinline] +private predicate multiple_invocation_paths(FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi) { + i1 != i2 and + i1 = top.getACallee+() and + i2 = top.getACallee+() and + i1.getFunction() = multi and + i2.getFunction() = multi +} + +/** Holds if `self.name` calls `multi` by multiple paths, and thus calls it more than once. */ +predicate multiple_calls_to_superclass_method(ClassObject self, FunctionObject multi, string name) { + exists(FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2 | + multiple_invocation_paths(top, i1, i2, multi) and + top.runtime(self.declaredAttribute(name)) and + self.getASuperType().declaredAttribute(name) = multi | + /* Only called twice if called from different functions, + * or if one call-site can reach the other */ + i1.getCall().getScope() != i2.getCall().getScope() + or + i1.getCall().strictlyReaches(i2.getCall()) + ) +} + +/** Holds if all attributes called `name` can be inferred to be methods. */ +private predicate named_attributes_not_method(ClassObject cls, string name) { + cls.declaresAttribute(name) and not cls.declaredAttribute(name) instanceof FunctionObject +} + +/** Holds if `f` actually does something. */ +private predicate does_something(FunctionObject f) { + f.isBuiltin() and not f = theObjectType().lookupAttribute("__init__") + or + exists(Stmt s | s = f.getFunction().getAStmt() and not s instanceof Pass) +} + +/** Holds if `meth` looks like it should have a call to `name`, but does not */ +private predicate missing_call(FunctionObject meth, string name) { + exists(CallNode call, AttrNode attr | + call.getScope() = meth.getFunction() and + call.getFunction() = attr and + attr.getName() = name and + not exists(FunctionObject f | f.getACall() = call) + ) +} + +/** Holds if `self.name` does not call `missing`, even though it is expected to. */ +predicate missing_call_to_superclass_method(ClassObject self, FunctionObject top, FunctionObject missing, string name) { + missing = self.getASuperType().declaredAttribute(name) and + top = self.lookupAttribute(name) and + /* There is no call to missing originating from top */ + not top.getACallee*() = missing and + /* Make sure that all named 'methods' are objects that we can understand. */ + not exists(ClassObject sup | + sup = self.getAnImproperSuperType() and + named_attributes_not_method(sup, name) + ) and + not self.isAbstract() + and + does_something(missing) + and + not missing_call(top, name) +} + diff --git a/python/ql/src/Classes/MissingCallToDel.py b/python/ql/src/Classes/MissingCallToDel.py new file mode 100644 index 000000000000..37520071b3ae --- /dev/null +++ b/python/ql/src/Classes/MissingCallToDel.py @@ -0,0 +1,26 @@ + +class Vehicle(object): + + def __del__(self): + recycle(self.base_parts) + +class Car(Vehicle): + + def __del__(self): + recycle(self.car_parts) + Vehicle.__del__(self) + +#Car.__del__ is missed out. +class SportsCar(Car, Vehicle): + + def __del__(self): + recycle(self.sports_car_parts) + Vehicle.__del__(self) + +#Fix SportsCar by calling Car.__del__ +class FixedSportsCar(Car, Vehicle): + + def __del__(self): + recycle(self.sports_car_parts) + Car.__del__(self) + diff --git a/python/ql/src/Classes/MissingCallToDel.qhelp b/python/ql/src/Classes/MissingCallToDel.qhelp new file mode 100644 index 000000000000..864ddd1b56b8 --- /dev/null +++ b/python/ql/src/Classes/MissingCallToDel.qhelp @@ -0,0 +1,50 @@ + + + + +

    Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object destruction. +However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies. +Therefore the developer has responsibility for ensuring that objects are properly cleaned up when +there are multiple __del__ methods that need to be called. +

    +

    +If the __del__ method of a superclass is not called during object destruction it is likely that +that resources may be leaked. +

    + +

    A call to the __del__ method of a superclass during object destruction may be omitted: +

    +
      +
    • When a subclass calls the __del__ method of the wrong class.
    • +
    • When a call to the __del__ method of one its base classes is omitted.
    • +
    + + +
    + +

    Either be careful to explicitly call the __del__ of the correct base class, or +use super() throughout the inheritance hierarchy.

    + +

    Alternatively refactor one or more of the classes to use composition rather than inheritance.

    + + +
    + +

    In this example, explicit calls to __del__ are used, but SportsCar erroneously calls +Vehicle.__del__. This is fixed in FixedSportsCar by calling Car.__del__. +

    + + + +
    + + +
  • Python Tutorial: Classes.
  • +
  • Python Standard Library: super.
  • +
  • Artima Developer: Things to Know About Python Super.
  • +
  • Wikipedia: Composition over inheritance.
  • + +
    +
    diff --git a/python/ql/src/Classes/MissingCallToDel.ql b/python/ql/src/Classes/MissingCallToDel.ql new file mode 100644 index 000000000000..d08c83996692 --- /dev/null +++ b/python/ql/src/Classes/MissingCallToDel.ql @@ -0,0 +1,26 @@ +/** + * @name Missing call to __del__ during object destruction + * @description An omitted call to a super-class __del__ method may lead to class instances not being cleaned up properly. + * @kind problem + * @tags efficiency + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/missing-call-to-delete + */ + +import python +import MethodCallOrder + + +from ClassObject self, FunctionObject missing + +where + missing_call_to_superclass_method(self, _, missing, "__del__") and + not missing.neverReturns() and + not self.failedInference() and + not missing.isBuiltin() +select self, "Class " + self.getName() + " may not be cleaned up properly as $@ is not called during deletion.", +missing, missing.descriptiveString() + diff --git a/python/ql/src/Classes/MissingCallToInit.py b/python/ql/src/Classes/MissingCallToInit.py new file mode 100644 index 000000000000..1b3e0e3aee59 --- /dev/null +++ b/python/ql/src/Classes/MissingCallToInit.py @@ -0,0 +1,26 @@ + +class Vehicle(object): + + def __init__(self): + self.mobile = True + +class Car(Vehicle): + + def __init__(self): + Vehicle.__init__(self) + self.car_init() + +#Car.__init__ is missed out. +class SportsCar(Car, Vehicle): + + def __init__(self): + Vehicle.__init__(self) + self.sports_car_init() + +#Fix SportsCar by calling Car.__init__ +class FixedSportsCar(Car, Vehicle): + + def __init__(self): + Car.__init__(self) + self.sports_car_init() + diff --git a/python/ql/src/Classes/MissingCallToInit.qhelp b/python/ql/src/Classes/MissingCallToInit.qhelp new file mode 100644 index 000000000000..31ad3d693a5d --- /dev/null +++ b/python/ql/src/Classes/MissingCallToInit.qhelp @@ -0,0 +1,52 @@ + + + + +

    Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object initialization. +However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies. +Therefore the developer has responsibility for ensuring that objects are properly initialized when +there are multiple __init__ methods that need to be called. +

    +

    +If the __init__ method of a superclass is not called during object initialization it is likely that +that object will end up in an incorrect state. +

    + +

    A call to the __init__ method of a superclass during object initialization may be omitted: +

    +
      +
    • When a subclass calls the __init__ method of the wrong class.
    • +
    • When a call to the __init__ method of one its base classes is omitted.
    • +
    • When multiple inheritance is used and a class inherits from several base classes, + and at least one of those does not use super() in its own __init__ method.
    • +
    + + +
    + +

    Either be careful to explicitly call the __init__ of the correct base class, or +use super() throughout the inheritance hierarchy.

    + +

    Alternatively refactor one or more of the classes to use composition rather than inheritance.

    + + +
    + +

    In this example, explicit calls to __init__ are used, but SportsCar erroneously calls +Vehicle.__init__. This is fixed in FixedSportsCar by calling Car.__init__. +

    + + + +
    + + +
  • Python Tutorial: Classes.
  • +
  • Python Standard Library: super.
  • +
  • Artima Developer: Things to Know About Python Super.
  • +
  • Wikipedia: Composition over inheritance.
  • + +
    +
    diff --git a/python/ql/src/Classes/MissingCallToInit.ql b/python/ql/src/Classes/MissingCallToInit.ql new file mode 100644 index 000000000000..ad137f817f4b --- /dev/null +++ b/python/ql/src/Classes/MissingCallToInit.ql @@ -0,0 +1,28 @@ +/** + * @name Missing call to __init__ during object initialization + * @description An omitted call to a super-class __init__ method may lead to objects of this class not being fully initialized. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/missing-call-to-init + */ + +import python +import MethodCallOrder + +from ClassObject self, FunctionObject initializer, FunctionObject missing + +where + self.lookupAttribute("__init__") = initializer and + missing_call_to_superclass_method(self, initializer, missing, "__init__") and + // If a superclass is incorrect, don't flag this class as well. + not missing_call_to_superclass_method(self.getASuperType(), _, missing, "__init__") and + not missing.neverReturns() and + not self.failedInference() and + not missing.isBuiltin() and + not self.isAbstract() +select self, "Class " + self.getName() + " may not be initialized properly as $@ is not called from its $@.", +missing, missing.descriptiveString(), initializer, "__init__ method" \ No newline at end of file diff --git a/python/ql/src/Classes/MutatingDescriptor.py b/python/ql/src/Classes/MutatingDescriptor.py new file mode 100644 index 000000000000..eb73aace928d --- /dev/null +++ b/python/ql/src/Classes/MutatingDescriptor.py @@ -0,0 +1,40 @@ + +#This is prone to strange side effects and race conditions. +class MutatingDescriptor(object): + + def __init__(self, func): + self.my_func = func + + def __get__(self, obj, obj_type): + #Modified state is visible to all instances of C that might call "show". + self.my_obj = obj + return self + + def __call__(self, *args): + return self.my_func(self.my_obj, *args) + +def show(obj): + print (obj) + +class C(object): + + def __init__(self, value): + self.value = value + + def __str__(self): + return ("C: " + str(self.value)) + + show = MutatingDescriptor(show) + +c1 = C(1) +c1.show() +c2 = C(2) +c2.show() +c1_show = c1.show +c2.show +c1_show() + +#Outputs: +#C: 1 +#C: 2 +#C: 2 \ No newline at end of file diff --git a/python/ql/src/Classes/MutatingDescriptor.qhelp b/python/ql/src/Classes/MutatingDescriptor.qhelp new file mode 100644 index 000000000000..a424c47dfb0a --- /dev/null +++ b/python/ql/src/Classes/MutatingDescriptor.qhelp @@ -0,0 +1,42 @@ + + + + +

    The descriptor protocol allows user programmable attribute access. +The descriptor protocol is what enables class methods, static methods, properties and super(). +

    + +

    +Descriptor objects are class attributes which control the behavior of instance attributes. Consequently, a single descriptor +is common to all instances of a class and should not be mutated when instance attributes are accessed. +

    + + +
    + + +

    Do not mutate the descriptor object, rather create a new object that contains the necessary state.

    + + +
    + +

    In this example the descriptor class MutatingDescriptor stores a reference to obj in an attribute. +

    + +

    In the following example, the descriptor class NonMutatingDescriptor returns a new object every time __get__ +is called. + +

    + +
    + + +
  • Python Language Reference: Implementing Descriptors.
  • +
  • Mark Lutz. Learning Python, Section 30.6: Methods Are Objects: Bound or Unbound. O'Reilly 2013.
  • +
  • A real world example: NumPy Issue 5247.
  • + + +
    +
    diff --git a/python/ql/src/Classes/MutatingDescriptor.ql b/python/ql/src/Classes/MutatingDescriptor.ql new file mode 100644 index 000000000000..328c1fe86ab3 --- /dev/null +++ b/python/ql/src/Classes/MutatingDescriptor.ql @@ -0,0 +1,28 @@ +/** + * @name Mutation of descriptor in __get__ or __set__ method. + * @description Descriptor objects can be shared across many instances. Mutating them can cause strange side effects or race conditions. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/mutable-descriptor + */ + +import python + +predicate mutates_descriptor(ClassObject cls, SelfAttributeStore s) { + cls.isDescriptorType() and + exists(PyFunctionObject f | + cls.lookupAttribute(_) = f and + not f.getName() = "__init__" and + s.getScope() = f.getFunction() + ) +} + +from ClassObject cls, SelfAttributeStore s +where +mutates_descriptor(cls, s) + +select s, "Mutation of descriptor $@ object may lead to action-at-a-distance effects or race conditions for properties.", cls, cls.getName() \ No newline at end of file diff --git a/python/ql/src/Classes/MutatingDescriptorFixed.py b/python/ql/src/Classes/MutatingDescriptorFixed.py new file mode 100644 index 000000000000..113a3e69d351 --- /dev/null +++ b/python/ql/src/Classes/MutatingDescriptorFixed.py @@ -0,0 +1,37 @@ +import types + +#Immutable version, which is safe to share. +class NonMutatingDescriptor(object): + + def __init__(self, func): + self.my_func = func + + def __get__(self, obj, obj_type): + #Return a new object to each access. + return types.MethodType(self.my_func, obj) + +def show(obj): + print (obj) + +class C(object): + + def __init__(self, value): + self.value = value + + def __str__(self): + return ("C: " + str(self.value)) + + show = NonMutatingDescriptor(show) + +c1 = C(1) +c1.show() +c2 = C(2) +c2.show() +c1_show = c1.show +c2.show +c1_show() + +#Outputs: +#C: 1 +#C: 2 +#C: 1 \ No newline at end of file diff --git a/python/ql/src/Classes/OverwritingAttributeInSuperClass.py b/python/ql/src/Classes/OverwritingAttributeInSuperClass.py new file mode 100644 index 000000000000..4322093634ee --- /dev/null +++ b/python/ql/src/Classes/OverwritingAttributeInSuperClass.py @@ -0,0 +1,35 @@ + +#Attribute set in both superclass and subclass +class C(object): + + def __init__(self): + self.var = 0 + +class D(C): + + def __init__(self): + self.var = 1 # self.var will be overwritten + C.__init__(self) + +class E(object): + + def __init__(self): + self.var = 0 # self.var will be overwritten + +class F(E): + + def __init__(self): + E.__init__(self) + self.var = 1 + +#Fixed version -- Pass explicitly as a parameter +class G(object): + + def __init__(self, var = 0): + self.var = var + +class H(G): + + def __init__(self): + G.__init__(self, 1) + diff --git a/python/ql/src/Classes/OverwritingAttributeInSuperClass.qhelp b/python/ql/src/Classes/OverwritingAttributeInSuperClass.qhelp new file mode 100644 index 000000000000..667ad35e02d5 --- /dev/null +++ b/python/ql/src/Classes/OverwritingAttributeInSuperClass.qhelp @@ -0,0 +1,29 @@ + + + +

    +Subclasses should not set attributes that are set in the superclass. +Doing so may violate invariants in the superclass.

    + +
    + + +

    +If you did not intend to override the attribute value set in the superclass, +then rename the subclass attribute. +If you do want to be able to set a new value for the attribute of the superclass, +then convert the superclass attribute to a property. +Otherwise the value should be passed as a parameter to the superclass +__init__ method. +

    + +
    + + + + + + +
    diff --git a/python/ql/src/Classes/OverwritingAttributeInSuperClass.ql b/python/ql/src/Classes/OverwritingAttributeInSuperClass.ql new file mode 100644 index 000000000000..bd2be2ed3793 --- /dev/null +++ b/python/ql/src/Classes/OverwritingAttributeInSuperClass.ql @@ -0,0 +1,71 @@ +/** + * @name Overwriting attribute in super-class or sub-class + * @description Assignment to self attribute overwrites attribute previously defined in subclass or superclass __init__ method. + * @kind problem + * @tags reliability + * maintainability + * modularity + * @problem.severity warning + * @sub-severity low + * @precision medium + * @id py/overwritten-inherited-attribute + */ + +import python + + +class InitCallStmt extends ExprStmt { + + InitCallStmt() { + exists(Call call, Attribute attr | call = this.getValue() and attr = call.getFunc() | + attr.getName() = "__init__") + } + +} + +predicate overwrites_which(Function subinit, AssignStmt write_attr, string which) { + write_attr.getScope() = subinit and self_write_stmt(write_attr, _) and + exists(Stmt top | top.contains(write_attr) or top = write_attr | + (exists(int i, int j, InitCallStmt call | call.getScope() = subinit | i > j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "superclass") + or + exists(int i, int j, InitCallStmt call | call.getScope() = subinit | i < j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "subclass") + ) + ) +} + +predicate self_write_stmt(Stmt s, string attr) { + exists(Attribute a, Name self | self = a.getObject() and s.contains(a) and self.getId() = "self" and a.getCtx() instanceof Store and a.getName() = attr) +} + +predicate both_assign_attribute(Stmt s1, Stmt s2, Function f1, Function f2) { + exists(string name | s1.getScope() = f1 and s2.getScope() = f2 and self_write_stmt(s1, name) and self_write_stmt(s2, name)) +} + +predicate attribute_overwritten(AssignStmt overwrites, AssignStmt overwritten, string name, string classtype, string classname) +{ + exists(FunctionObject superinit, FunctionObject subinit, ClassObject superclass, ClassObject subclass, AssignStmt subattr, AssignStmt superattr | + (classtype = "superclass" and classname = superclass.getName() and overwrites = subattr and overwritten = superattr or + classtype = "subclass" and classname = subclass.getName() and overwrites = superattr and overwritten = subattr) + and + /* OK if overwritten in subclass and is a class attribute */ + (not exists(superclass.declaredAttribute(name)) or classtype = "subclass") + and + superclass.declaredAttribute("__init__") = superinit + and + subclass.declaredAttribute("__init__") = subinit + and + superclass = subclass.getASuperType() + and + overwrites_which(subinit.getFunction(), subattr, classtype) + and + both_assign_attribute(subattr, superattr, subinit.getFunction(), superinit.getFunction()) + and + self_write_stmt(superattr, name) + ) +} + + +from string classtype, AssignStmt overwrites, AssignStmt overwritten, string name, string classname +where attribute_overwritten(overwrites, overwritten, name, classtype, classname) + +select overwrites, "Assignment overwrites attribute " + name + ", which was previously defined in " + classtype + " $@.", overwritten, classname diff --git a/python/ql/src/Classes/PropertyInOldStyleClass.py b/python/ql/src/Classes/PropertyInOldStyleClass.py new file mode 100644 index 000000000000..73ab9ac7ae29 --- /dev/null +++ b/python/ql/src/Classes/PropertyInOldStyleClass.py @@ -0,0 +1,42 @@ + +class OldStyle: + + def __init__(self, x): + self._x = x + + # Incorrect: 'OldStyle' is not a new-style class and '@property' is not supported + @property + def x(self): + return self._x + + +class InheritOldStyle(OldStyle): + + def __init__(self, x): + self._x = x + + # Incorrect: 'InheritOldStyle' is not a new-style class and '@property' is not supported + @property + def x(self): + return self._x + + +class NewStyle(object): + + def __init__(self, x): + self._x = x + + # Correct: 'NewStyle' is a new-style class and '@property' is supported + @property + def x(self): + return self._x + +class InheritNewStyle(NewStyle): + + def __init__(self, x): + self._x = x + + # Correct: 'InheritNewStyle' inherits from a new-style class and '@property' is supported + @property + def x(self): + return self._x diff --git a/python/ql/src/Classes/PropertyInOldStyleClass.qhelp b/python/ql/src/Classes/PropertyInOldStyleClass.qhelp new file mode 100644 index 000000000000..1236cb785c9d --- /dev/null +++ b/python/ql/src/Classes/PropertyInOldStyleClass.qhelp @@ -0,0 +1,43 @@ + + + + + +

    Property descriptors are only supported for the new-style classes that were introduced in Python +2.1. Property descriptors should only be used in new-style classes.

    + +
    + + +

    If you want to define properties in a class, then ensure that the class is a new-style class. You can +convert an old-style class to a new-style class by inheriting from object.

    + +
    + +

    In the following example all the classes attempt to set a property for x. However, only +the third and fourth classes are new-style classes. Consequently, the x +property is only available for the NewStyle and InheritNewStyle classes.

    + +

    If you define the OldStyle class as inheriting from a new-style class, then the x + property would be available for both the OldStyle and InheritOldStyle classes.

    + + + + +
    + + +
  • Python Glossary: New-style class.
  • +
  • Python Language Reference: New-style and classic +classes, + +Descriptors.
  • +
  • Python Standard Library: Property.
  • +
  • The History of Python: +Inside story on new-style classes.
  • + + +
    +
    diff --git a/python/ql/src/Classes/PropertyInOldStyleClass.ql b/python/ql/src/Classes/PropertyInOldStyleClass.ql new file mode 100644 index 000000000000..fb2c822a5737 --- /dev/null +++ b/python/ql/src/Classes/PropertyInOldStyleClass.ql @@ -0,0 +1,17 @@ +/** + * @name Property in old-style class + * @description Using property descriptors in old-style classes does not work from Python 2.1 onward. + * @kind problem + * @tags portability + * correctness + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/property-in-old-style-class + */ + +import python + +from PropertyObject prop, ClassObject cls +where cls.declaredAttribute(_) = prop and not cls.failedInference() and not cls.isNewStyle() +select prop, "Property " + prop.getName() + " will not work properly, as class " + cls.getName() + " is an old-style class." diff --git a/python/ql/src/Classes/ShouldBeContextManager.py b/python/ql/src/Classes/ShouldBeContextManager.py new file mode 100644 index 000000000000..94e890192e36 --- /dev/null +++ b/python/ql/src/Classes/ShouldBeContextManager.py @@ -0,0 +1,33 @@ +class remotelock(object): # Resources can be released using __del__ + + def __init__(self, repo): + self.repo = repo + + def release(self): + self.repo.unlock() + self.repo = None + + def __del__(self): + if self.repo: + self.release() + + +class remotelock2(object): # Resources can be released using a with statement + + def __init__(self, repo): + self.repo = repo + + def __enter__(self): + return self + + def release(self): + self.repo.unlock() + self.repo = None + + def __del__(self): + if self.repo: + self.release() + + def __exit__(self, exct_type, exce_value, traceback): + if self.repo: + self.release() diff --git a/python/ql/src/Classes/ShouldBeContextManager.qhelp b/python/ql/src/Classes/ShouldBeContextManager.qhelp new file mode 100644 index 000000000000..7c497688499c --- /dev/null +++ b/python/ql/src/Classes/ShouldBeContextManager.qhelp @@ -0,0 +1,42 @@ + + + +

    If a class has a close() or similar method to release resources, then it +should be made a context manager. Using a context manager allows instances of the class to be used +in the with statement, improving code size and readability. This is a simpler and more +reliable method than implementing just a __del__ method. +

    + +
    + +

    The context manager requires an __enter__ and an __exit__ method:

    +
      +
    • +__enter__ method acquires the resource or does nothing if the resource +is acquired in the __init__ method
    • +
    • __exit__ method releases the resource, this can just be a simple wrapper around the + close method.
    • +
    + +
    + +

    The following example shows how a class definition that implements __del__ can be +updated to use a context manager.

    + + + + +
    + + +
  • Effbot: Python with statement.
  • +
  • Python Standard Library: Context manager +.
  • +
  • Python Language Reference: +With Statement Context Managers.
  • +
  • Python PEP 343: The "with" Statement.
  • + +
    +
    diff --git a/python/ql/src/Classes/ShouldBeContextManager.ql b/python/ql/src/Classes/ShouldBeContextManager.ql new file mode 100644 index 000000000000..d7394728935e --- /dev/null +++ b/python/ql/src/Classes/ShouldBeContextManager.ql @@ -0,0 +1,19 @@ +/** + * @name Class should be a context manager + * @description Making a class a context manager allows instances to be used in a 'with' statement. + * This improves resource handling and code readability. + * @kind problem + * @tags maintainability + * readability + * convention + * @problem.severity recommendation + * @sub-severity high + * @precision medium + * @id py/should-be-context-manager + */ + +import python + +from ClassObject c +where not c.isC() and not c.isContextManager() and exists(c.declaredAttribute("__del__")) +select c, "Class " + c.getName() + " implements __del__ (presumably to release some resource). Consider making it a context manager." diff --git a/python/ql/src/Classes/SlotsInOldStyleClass.py b/python/ql/src/Classes/SlotsInOldStyleClass.py new file mode 100644 index 000000000000..67858d9fd4e6 --- /dev/null +++ b/python/ql/src/Classes/SlotsInOldStyleClass.py @@ -0,0 +1,20 @@ +class Point: + + __slots__ = [ '_x', '_y' ] # Incorrect: 'Point' is an old-style class. + # No slots are created. + # Instances of Point have an attribute dictionary. + + def __init__(self, x, y): + self._x = x + self._y = y + + +class Point2(object): + + __slots__ = [ '_x', '_y' ] # Correct: 'Point2' is an new-style class + # Two slots '_x' and '_y' are created. + # Instances of Point2 have no attribute dictionary. + + def __init__(self, x, y): + self._x = x + self._y = y diff --git a/python/ql/src/Classes/SlotsInOldStyleClass.qhelp b/python/ql/src/Classes/SlotsInOldStyleClass.qhelp new file mode 100644 index 000000000000..eb7208d62579 --- /dev/null +++ b/python/ql/src/Classes/SlotsInOldStyleClass.qhelp @@ -0,0 +1,37 @@ + + + + +

    The ability to override the class dictionary using a __slots__ declaration +is supported only by new-style classes. When you add a __slots__ declaration to an +old-style class it just creates a class attribute called '__slots__'.

    + +
    + + +

    If you want to override the dictionary for a class, then ensure that the class is a new-style class. +You can convert an old-style class to a new-style class by inheriting from object.

    + +
    + +

    In the following example the KeyedRef class is an old-style class (no inheritance). The +__slots__ declaration in this class creates a class attribute called '__slots__', the class +dictionary is unaffected. The KeyedRef2 class is a new-style class so the +__slots__ declaration causes special compact attributes to be created for each name in +the slots list and saves space by not creating attribute dictionaries.

    + + + +
    + + +
  • Python Glossary: New-style class.
  • +
  • Python Language Reference: New-style and classic +classes, + __slots__.
  • + + +
    +
    diff --git a/python/ql/src/Classes/SlotsInOldStyleClass.ql b/python/ql/src/Classes/SlotsInOldStyleClass.ql new file mode 100644 index 000000000000..78b9bd18f294 --- /dev/null +++ b/python/ql/src/Classes/SlotsInOldStyleClass.ql @@ -0,0 +1,18 @@ +/** + * @name '__slots__' in old-style class + * @description Overriding the class dictionary by declaring '__slots__' is not supported by old-style + * classes. + * @kind problem + * @problem.severity error + * @tags portability + * correctness + * @sub-severity low + * @precision very-high + * @id py/slots-in-old-style-class + */ + +import python + +from ClassObject c +where not c.isNewStyle() and c.declaresAttribute("__slots__") and not c.failedInference() +select c, "Using __slots__ in an old style class just creates a class attribute called '__slots__'" diff --git a/python/ql/src/Classes/SubclassShadowing.py b/python/ql/src/Classes/SubclassShadowing.py new file mode 100644 index 000000000000..617db3c58e0b --- /dev/null +++ b/python/ql/src/Classes/SubclassShadowing.py @@ -0,0 +1,17 @@ +class Mammal(object): + + def __init__(self, milk = 0): + self.milk = milk + + +class Cow(Mammal): + + def __init__(self): + Mammal.__init__(self) + + def milk(self): + return "Milk" + +#Cow().milk() will raise an error as Cow().milk is the 'milk' attribute +#set in Mammal.__init__, not the 'milk' method defined on Cow. + diff --git a/python/ql/src/Classes/SubclassShadowing.qhelp b/python/ql/src/Classes/SubclassShadowing.qhelp new file mode 100644 index 000000000000..90daa9a992ab --- /dev/null +++ b/python/ql/src/Classes/SubclassShadowing.qhelp @@ -0,0 +1,27 @@ + + + +

    Subclass shadowing occurs when an instance attribute of a superclass has the +the same name as a method of a subclass, or vice-versa. +The semantics of Python attribute look-up mean that the instance attribute of +the superclass hides the method in the subclass. +

    + +
    + + +

    Rename the method in the subclass or rename the attribute in the superclass.

    + +
    + +

    The following code includes an example of subclass shadowing. When you call Cow().milk() +an error is raised because Cow().milk is interpreted as the 'milk' attribute set in +Mammal.__init__, not the 'milk' method defined within Cow. This can be fixed +by changing the name of either the 'milk' attribute or the 'milk' method.

    + + + +
    +
    diff --git a/python/ql/src/Classes/SubclassShadowing.ql b/python/ql/src/Classes/SubclassShadowing.ql new file mode 100644 index 000000000000..6cdd9edf01d2 --- /dev/null +++ b/python/ql/src/Classes/SubclassShadowing.ql @@ -0,0 +1,40 @@ +/** + * @name Superclass attribute shadows subclass method + * @description Defining an attribute in a superclass method with a name that matches a subclass + * method, hides the method in the subclass. + * @kind problem + * @problem.severity error + * @tags maintainability + * correctness + * @sub-severity low + * @precision high + * @id py/attribute-shadows-method + */ + +/* Determine if a class defines a method that is shadowed by an attribute + defined in a super-class +*/ + +/* Need to find attributes defined in superclass (only in __init__?) */ + +import python + +predicate shadowed_by_super_class(ClassObject c, ClassObject supercls, Assign assign, FunctionObject f) +{ + c.getASuperType() = supercls and c.declaredAttribute(_) = f and + exists(FunctionObject init, Attribute attr | + supercls.declaredAttribute("__init__") = init and + attr = assign.getATarget() and + ((Name)attr.getObject()).getId() = "self" and + attr.getName() = f.getName() and + assign.getScope() = ((FunctionExpr)init.getOrigin()).getInnerScope() + ) and + /* It's OK if the super class defines the method as well. + * We assume that the original method must have been defined for a reason. */ + not supercls.hasAttribute(f.getName()) +} + +from ClassObject c, ClassObject supercls, Assign assign, FunctionObject shadowed +where shadowed_by_super_class(c, supercls, assign, shadowed) +select shadowed.getOrigin(), "Method " + shadowed.getName() + " is shadowed by $@ in super class '"+ supercls.getName() + "'.", assign, "an attribute" + diff --git a/python/ql/src/Classes/SuperInOldStyleClass.py b/python/ql/src/Classes/SuperInOldStyleClass.py new file mode 100644 index 000000000000..815b98167355 --- /dev/null +++ b/python/ql/src/Classes/SuperInOldStyleClass.py @@ -0,0 +1,18 @@ +class PythonModule(_ModuleIteratorHelper): # '_ModuleIteratorHelper' and 'PythonModule' are old-style classes + + # class definitions .... + + def walkModules(self, importPackages=False): + if importPackages and self.isPackage(): + self.load() + return super(PythonModule, self).walkModules(importPackages=importPackages) # super() will fail + + +class PythonModule2(_ModuleIteratorHelper): # call to super replaced with direct call to class + + # class definitions .... + + def walkModules(self, importPackages=False): + if importPackages and self.isPackage(): + self.load() + return _ModuleIteratorHelper.__init__(PythonModule, self).walkModules(importPackages=importPackages) diff --git a/python/ql/src/Classes/SuperInOldStyleClass.qhelp b/python/ql/src/Classes/SuperInOldStyleClass.qhelp new file mode 100644 index 000000000000..6899c08dc21c --- /dev/null +++ b/python/ql/src/Classes/SuperInOldStyleClass.qhelp @@ -0,0 +1,38 @@ + + + + +

    The ability to access inherited methods that have been overridden in a class using super() +is supported only by new-style classes. When you use the super() function in an old-style +class it fails.

    + +
    + +

    If you want to access inherited methods using the super() built-in, then ensure that +the class is a new-style class. You can convert an old-style class to a new-style class by inheriting +from object. Alternatively, you can call the __init__ method of the superclass +directly from an old-style class using: BaseClass.__init__(...).

    + +
    + +

    In the following example, PythonModule is an old-style class as it inherits from another +old-style class. If the _ModuleIteratorHelper class cannot be converted into a new-style +class, then the call to super() must be replaced. The PythonModule2 class +demonstrates the correct way to call a superclass from an old-style class.

    + + + + +
    + + +
  • Python Glossary: New-style class.
  • +
  • Python Language Reference: New-style and classic +classes.
  • +
  • Python Standard Library: super.
  • + + +
    +
    diff --git a/python/ql/src/Classes/SuperInOldStyleClass.ql b/python/ql/src/Classes/SuperInOldStyleClass.ql new file mode 100644 index 000000000000..b6c7649a1ca6 --- /dev/null +++ b/python/ql/src/Classes/SuperInOldStyleClass.ql @@ -0,0 +1,22 @@ +/** + * @name 'super' in old style class + * @description Using super() to access inherited methods is not supported by old-style classes. + * @kind problem + * @tags portability + * correctness + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/super-in-old-style + */ + +import python + +predicate uses_of_super_in_old_style_class(Call s) { + exists(Function f, ClassObject c | s.getScope() = f and f.getScope() = c.getPyClass() and not c.failedInference() and + not c.isNewStyle() and ((Name)s.getFunc()).getId() = "super") +} + +from Call c +where uses_of_super_in_old_style_class(c) +select c, "super() will not work in old-style classes" \ No newline at end of file diff --git a/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.py b/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.py new file mode 100644 index 000000000000..0ee6e61bcb1a --- /dev/null +++ b/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.py @@ -0,0 +1,29 @@ +#Calling a method multiple times by using explicit calls when a base inherits from other base +class Vehicle(object): + + def __del__(self): + recycle(self.base_parts) + + +class Car(Vehicle): + + def __del__(self): + recycle(self.car_parts) + Vehicle.__del__(self) + + +class SportsCar(Car, Vehicle): + + # Vehicle.__del__ will get called twice + def __del__(self): + recycle(self.sports_car_parts) + Car.__del__(self) + Vehicle.__del__(self) + + +#Fix SportsCar by only calling Car.__del__ +class FixedSportsCar(Car, Vehicle): + + def __del__(self): + recycle(self.sports_car_parts) + Car.__del__(self) diff --git a/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.qhelp b/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.qhelp new file mode 100644 index 000000000000..d9514b2c68c4 --- /dev/null +++ b/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.qhelp @@ -0,0 +1,58 @@ + + + + +

    Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object destruction. +However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies. +Therefore the developer has responsibility for ensuring that objects are properly cleaned up when +there are multiple __del__ methods that need to be called. +

    + +

    +Calling a __del__ method more than once during object destruction risks resources being released multiple +times. The relevant __del__ method may not be designed to be called more than once. +

    + +

    There are a number of ways that a __del__ method may be be called more than once.

    +
      +
    • There may be more than one explicit call to the method in the hierarchy of __del__ methods.
    • +
    • A class using multiple inheritance directly calls the __del__ methods of its base types. + One or more of those base types uses super() to pass down the inheritance chain.
    • +
    + + +
    + +

    Either be careful not to explicitly call a __del__ method more than once, or +use super() throughout the inheritance hierarchy.

    + +

    Alternatively refactor one or more of the classes to use composition rather than inheritance.

    + +
    + +

    In the first example, explicit calls to __del__ are used, but SportsCar erroneously calls +both Vehicle.__del__ and Car.__del__. +This can be fixed by removing the call to Vehicle.__del__, as shown in FixedSportsCar. +

    + + + +

    In the second example, there is a mixture of explicit calls to __del__ and calls using super(). +To fix this example, super() should be used throughout. +

    + + + +
    + + +
  • Python Tutorial: Classes.
  • +
  • Python Standard Library: super.
  • +
  • Artima Developer: Things to Know About Python Super.
  • +
  • Wikipedia: Composition over inheritance.
  • + + +
    +
    diff --git a/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql b/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql new file mode 100644 index 000000000000..b0e4a13469d1 --- /dev/null +++ b/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql @@ -0,0 +1,27 @@ +/** + * @name Multiple calls to __del__ during object destruction + * @description A duplicated call to a super-class __del__ method may lead to class instances not be cleaned up properly. + * @kind problem + * @tags efficiency + * correctness + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/multiple-calls-to-delete + */ + +import python +import MethodCallOrder + + +from ClassObject self, FunctionObject multi +where +multiple_calls_to_superclass_method(self, multi, "__del__") and +not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__del__") and +not exists(FunctionObject better | + multiple_calls_to_superclass_method(self, better, "__del__") and + better.overrides(multi) +) and +not self.failedInference() +select self, "Class " + self.getName() + " may not be cleaned up properly as $@ may be called multiple times during destruction.", +multi, multi.descriptiveString() diff --git a/python/ql/src/Classes/SuperclassDelCalledMultipleTimes2.py b/python/ql/src/Classes/SuperclassDelCalledMultipleTimes2.py new file mode 100644 index 000000000000..8cb1433ac0c4 --- /dev/null +++ b/python/ql/src/Classes/SuperclassDelCalledMultipleTimes2.py @@ -0,0 +1,32 @@ + +#Calling a method multiple times by using explicit calls when a base uses super() +class Vehicle(object): + + def __del__(self): + recycle(self.base_parts) + super(Vehicle, self).__del__() + +class Car(Vehicle): + + def __del__(self): + recycle(self.car_parts) + super(Car, self).__del__() + + +class SportsCar(Car, Vehicle): + + # Vehicle.__del__ will get called twice + def __del__(self): + recycle(self.sports_car_parts) + Car.__del__(self) + Vehicle.__del__(self) + + +#Fix SportsCar by using super() +class FixedSportsCar(Car, Vehicle): + + def __del__(self): + recycle(self.sports_car_parts) + super(SportsCar, self).__del__() + + diff --git a/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.py b/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.py new file mode 100644 index 000000000000..050d5d389d61 --- /dev/null +++ b/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.py @@ -0,0 +1,36 @@ +#Calling a method multiple times by using explicit calls when a base inherits from other base +class Vehicle(object): + + def __init__(self): + self.mobile = True + +class Car(Vehicle): + + def __init__(self): + Vehicle.__init__(self) + self.car_init() + + def car_init(self): + pass + +class SportsCar(Car, Vehicle): + + # Vehicle.__init__ will get called twice + def __init__(self): + Vehicle.__init__(self) + Car.__init__(self) + self.sports_car_init() + + def sports_car_init(self): + pass + +#Fix SportsCar by only calling Car.__init__ +class FixedSportsCar(Car, Vehicle): + + def __init__(self): + Car.__init__(self) + self.sports_car_init() + + def sports_car_init(self): + pass + diff --git a/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.qhelp b/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.qhelp new file mode 100644 index 000000000000..f1140d68b891 --- /dev/null +++ b/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.qhelp @@ -0,0 +1,58 @@ + + + + +

    Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object initialization. +However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies. +Therefore the developer has responsibility for ensuring that objects are properly initialized when +there are multiple __init__ methods that need to be called. +

    + +

    +Calling an __init__ method more than once during object initialization risks the object being incorrectly initialized. +It is unlikely that the relevant __init__ method is designed to be called more than once. +

    + +

    There are a number of ways that an __init__ method may be be called more than once.

    +
      +
    • There may be more than one explicit call to the method in the hierarchy of __init__ methods.
    • +
    • A class using multiple inheritance directly calls the __init__ methods of its base types. + One or more of those base types uses super() to pass down the inheritance chain.
    • +
    + + +
    + +

    Either be careful not to explicitly call an __init__ method more than once, or +use super() throughout the inheritance hierarchy.

    + +

    Alternatively refactor one or more of the classes to use composition rather than inheritance.

    + +
    + +

    In the first example, explicit calls to __init__ are used, but SportsCar erroneously calls +both Vehicle.__init__ and Car.__init__. +This can be fixed by removing the call to Vehicle.__init__, as shown in FixedSportsCar. +

    + + + +

    In the second example, there is a mixture of explicit calls to __init__ and calls using super(). +To fix this example, super() should be used throughout. +

    + + + +
    + + +
  • Python Tutorial: Classes.
  • +
  • Python Standard Library: super.
  • +
  • Artima Developer: Things to Know About Python Super.
  • +
  • Wikipedia: Composition over inheritance.
  • + + +
    +
    diff --git a/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql b/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql new file mode 100644 index 000000000000..723527e1de84 --- /dev/null +++ b/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql @@ -0,0 +1,26 @@ +/** + * @name Multiple calls to __init__ during object initialization + * @description A duplicated call to a super-class __init__ method may lead to objects of this class not being properly initialized. + * @kind problem + * @tags reliability + * correctness + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/multiple-calls-to-init + */ + +import python +import MethodCallOrder + +from ClassObject self, FunctionObject multi +where multi != theObjectType().lookupAttribute("__init__") and +multiple_calls_to_superclass_method(self, multi, "__init__") and +not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__init__") and +not exists(FunctionObject better | + multiple_calls_to_superclass_method(self, better, "__init__") and + better.overrides(multi) +) and +not self.failedInference() +select self, "Class " + self.getName() + " may not be initialized properly as $@ may be called multiple times during initialization.", +multi, multi.descriptiveString() diff --git a/python/ql/src/Classes/SuperclassInitCalledMultipleTimes2.py b/python/ql/src/Classes/SuperclassInitCalledMultipleTimes2.py new file mode 100644 index 000000000000..e12e86a079ee --- /dev/null +++ b/python/ql/src/Classes/SuperclassInitCalledMultipleTimes2.py @@ -0,0 +1,38 @@ + +#Calling a method multiple times by using explicit calls when a base uses super() +class Vehicle(object): + + def __init__(self): + super(Vehicle, self).__init__() + self.mobile = True + +class Car(Vehicle): + + def __init__(self): + super(Car, self).__init__() + self.car_init() + + def car_init(self): + pass + +class SportsCar(Car, Vehicle): + + # Vehicle.__init__ will get called twice + def __init__(self): + Vehicle.__init__(self) + Car.__init__(self) + self.sports_car_init() + + def sports_car_init(self): + pass + +#Fix SportsCar by using super() +class FixedSportsCar(Car, Vehicle): + + def __init__(self): + super(SportsCar, self).__init__() + self.sports_car_init() + + def sports_car_init(self): + pass + diff --git a/python/ql/src/Classes/UndefinedClassAttribute.py b/python/ql/src/Classes/UndefinedClassAttribute.py new file mode 100644 index 000000000000..f94753eed4ff --- /dev/null +++ b/python/ql/src/Classes/UndefinedClassAttribute.py @@ -0,0 +1,19 @@ +class Spam: + + def __init__(self): + self.spam = 'spam, spam, spam' + + def __str__(self): + return '%s and %s' % (self.spam, self.eggs) # Uninitialized attribute 'eggs' + +#Fixed version + +class Spam: + + def __init__(self): + self.spam = 'spam, spam, spam' + self.eggs = None + + def __str__(self): + return '%s and %s' % (self.spam, self.eggs) # OK + diff --git a/python/ql/src/Classes/UndefinedClassAttribute.qhelp b/python/ql/src/Classes/UndefinedClassAttribute.qhelp new file mode 100644 index 000000000000..46804576d6c7 --- /dev/null +++ b/python/ql/src/Classes/UndefinedClassAttribute.qhelp @@ -0,0 +1,33 @@ + + + + + +

    A non-existent attribute of self is accessed in a method. +An attribute is treated as non-existent if it is not a class attribute +and it is not set in any method of the class. +This may result in an AttributeError at run time. + +

    + +
    + + +

    Ensure that all attributes are initialized in the __init__ method.

    + + +
    + + + + + + + +
  • Python Standard Library: exception AttributeError.
  • + + +
    +
    diff --git a/python/ql/src/Classes/UndefinedClassAttribute.ql b/python/ql/src/Classes/UndefinedClassAttribute.ql new file mode 100644 index 000000000000..6b5a8c878047 --- /dev/null +++ b/python/ql/src/Classes/UndefinedClassAttribute.ql @@ -0,0 +1,34 @@ +/** + * @name Undefined class attribute + * @description Accessing an attribute of 'self' that is not initialized anywhere in the class in the __init__ method may cause an AttributeError at runtime + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision low + * @id py/undefined-attribute + */ + +import python +import semmle.python.SelfAttribute + +predicate undefined_class_attribute(SelfAttributeRead a, CheckClass c, int line, string name) { + name = a.getName() and + not c.sometimesDefines(name) and + c.interestingUndefined(a) and + line = a.getLocation().getStartLine() and + not attribute_assigned_in_method(c.getAMethodCalledFromInit(), name) +} + +predicate report_undefined_class_attribute(Attribute a, ClassObject c, string name) { + exists(int line | + undefined_class_attribute(a, c, line, name) and + line = min(int x | undefined_class_attribute(_, c, x, name)) + ) +} + +from Attribute a, ClassObject c, string name +where report_undefined_class_attribute(a, c, name) +select a, "Attribute '" + name + "' is not defined in either the class body or in any method" + diff --git a/python/ql/src/Classes/UselessClass.py b/python/ql/src/Classes/UselessClass.py new file mode 100644 index 000000000000..816839176108 --- /dev/null +++ b/python/ql/src/Classes/UselessClass.py @@ -0,0 +1,16 @@ +class GCDFinder(object): + def __init__(self, a, b): + self.a = a + self.b = b + + def calculate(self): + a = self.a + b = self.b + while a != 0 and b != 0: + if a > b: + a = a % b + else: + b = b % a + if a == 0: + return b + return a \ No newline at end of file diff --git a/python/ql/src/Classes/UselessClass.qhelp b/python/ql/src/Classes/UselessClass.qhelp new file mode 100644 index 000000000000..8881aea59ef1 --- /dev/null +++ b/python/ql/src/Classes/UselessClass.qhelp @@ -0,0 +1,35 @@ + + + + + +

    If a class has only one public method (other than its __init__) +it should be replaced with a function. +

    + +
    + + +

    Convert the single public method into a function. +If there is an __init__ and it sets attributes on the self +then rename the __init__ method and remove the self parameter +Make the public method an inner function and return that.

    + +

    Delete the class.

    + +
    + +

    In this example the class only has a single method. This method does not need to be in its own +class. It should be a method on its own that takes a and b as parameters. +

    + + +
    + + +
  • Python: Classes.
  • + +
    +
    diff --git a/python/ql/src/Classes/UselessClass.ql b/python/ql/src/Classes/UselessClass.ql new file mode 100644 index 000000000000..e04ea103ad6e --- /dev/null +++ b/python/ql/src/Classes/UselessClass.ql @@ -0,0 +1,83 @@ +/** + * @name Useless class + * @description Class only defines one public method (apart from __init__ or __new__) and should be replaced by a function + * @kind problem + * @tags maintainability + * useless-code + * @problem.severity recommendation + * @sub-severity low + * @precision medium + * @id py/useless-class + */ + +import python + +predicate fewer_than_two_public_methods(Class cls, int methods) { + (methods = 0 or methods = 1) and + methods = count(Function f | f = cls.getAMethod() and not f = cls.getInitMethod()) +} + +predicate does_not_define_special_method(Class cls) { + not exists(Function f | f = cls.getAMethod() and f.isSpecialMethod()) +} + + +predicate no_inheritance(Class c) { + not exists(ClassObject cls, ClassObject other | + cls.getPyClass() = c and + other != theObjectType() | + other.getABaseType() = cls or + cls.getABaseType() = other + ) + and + not exists(Expr base | base = c.getABase() | + not base instanceof Name or ((Name)base).getId() != "object" + ) +} + +predicate is_decorated(Class c) { + exists(c.getADecorator()) +} + +predicate is_stateful(Class c) { + exists(Function method, ExprContext ctx | + method.getScope() = c and (ctx instanceof Store or ctx instanceof AugStore) | + exists(Subscript s | s.getScope() = method and s.getCtx() = ctx) + or + exists(Attribute a | a.getScope() = method and a.getCtx() = ctx) + ) + or + exists(Function method, Call call, Attribute a, string name | + method.getScope() = c and call.getScope() = method and + call.getFunc() = a and a.getName() = name | + name = "pop" or name = "remove" or name = "discard" or + name = "extend" or name = "append" + ) + +} + +predicate useless_class(Class c, int methods) { + c.isTopLevel() + and + c.isPublic() + and + no_inheritance(c) + and + fewer_than_two_public_methods(c, methods) + and + does_not_define_special_method(c) + and + not c.isProbableMixin() + and + not is_decorated(c) + and + not is_stateful(c) +} + +from Class c, int methods, string msg +where useless_class(c, methods) and +(methods = 1 and msg = "Class " + c.getName() + " defines only one public method, which should be replaced by a function." + or + methods = 0 and msg = "Class " + c.getName() + " defines no public methods and could be replaced with a namedtuple or dictionary." +) +select c, msg diff --git a/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.py b/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.py new file mode 100644 index 000000000000..89b909f3cc26 --- /dev/null +++ b/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.py @@ -0,0 +1,6 @@ +class Point(object): + def __init__(self, x, y): + self.x = x + self.y = y + +p = Point(x=1, yy=2) # TypeError: 'yy' is not a valid keyword argument diff --git a/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.qhelp b/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.qhelp new file mode 100644 index 000000000000..ff21b557e4be --- /dev/null +++ b/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.qhelp @@ -0,0 +1,35 @@ + + + +

    +Using a named argument whose name does not correspond to a parameter of the __init__ method of the class being instantiated, will result in a +TypeError at runtime. +

    + +
    + + +

    Check for typos in the name of the arguments and fix those. +If the name is clearly different, then this suggests a logical error. +The change required to correct the error will depend on whether the wrong argument has been +specified or whether the wrong class has been specified. +

    + +
    + + + + + + +
  • Python Glossary: Arguments.
  • +
  • Python Glossary: Parameters.
  • +
  • Python Programming FAQ: + What is the difference between arguments and parameters?.
  • +
  • The Python Language Reference: Data model: object.__init__
  • +
  • The Python Tutorial: Classes
  • + +
    +
    diff --git a/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql b/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql new file mode 100644 index 000000000000..33bf524ae94d --- /dev/null +++ b/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql @@ -0,0 +1,27 @@ +/** + * @name Wrong name for an argument in a class instantiation + * @description Using a named argument whose name does not correspond to a + * parameter of the __init__ method of the class being + * instantiated, will result in a TypeError at runtime. + * @kind problem + * @tags reliability + * correctness + * external/cwe/cwe-628 + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/call/wrong-named-class-argument + */ + +import python + +import Expressions.CallArgs + + +from Call call, ClassObject cls, string name, FunctionObject init +where + illegally_named_parameter(call, cls, name) + and init = get_function_or_initializer(cls) +select + call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init, init.getQualifiedName() + diff --git a/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.py b/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.py new file mode 100644 index 000000000000..8e1350e4b1d0 --- /dev/null +++ b/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.py @@ -0,0 +1,7 @@ +class Point(object): + def __init__(self, x, y): + self.x = x + self.y = y + +p = Point(1) # TypeError: too few arguments +p = Point(1,2,3) # TypeError: too many arguments diff --git a/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.qhelp b/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.qhelp new file mode 100644 index 000000000000..ca311912a787 --- /dev/null +++ b/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.qhelp @@ -0,0 +1,45 @@ + + + +

    + A call to the __init__ method of a class must supply an argument + for each parameter that does not have a default value defined, so: +

    +
      +
    • The minimum number of arguments is the number of parameters without default values.
    • +
    • The maximum number of arguments is the total number of parameters, unless + the class __init__ method takes a varargs (starred) parameter in + which case there is no limit.
    • +
    +
    + +

    If there are too few arguments then check to see which arguments have been omitted and supply values for those.

    + +

    If there are too many arguments then check to see if any have been added by mistake and remove those.

    + +

    + Also check where a comma has been inserted instead of an operator or a dot. + For example, the code is obj,attr when it should be obj.attr. +

    + +

    If it is not clear which are the missing or surplus arguments, then this suggests a logical error. +The fix will then depend on the nature of the error. +

    + +
    + + + + + + +
  • Python Glossary: Arguments.
  • +
  • Python Glossary: Parameters.
  • +
  • Python Programming FAQ: + What is the difference between arguments and parameters?.
  • +
  • The Python Language Reference: Data model: object.__init__
  • +
  • The Python Tutorial: Classes
  • +
    +
    diff --git a/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql b/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql new file mode 100644 index 000000000000..915856319e0d --- /dev/null +++ b/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql @@ -0,0 +1,25 @@ +/** + * @name Wrong number of arguments in a class instantiation + * @description Using too many or too few arguments in a call to the __init__ + * method of a class will result in a TypeError at runtime. + * @kind problem + * @tags reliability + * correctness + * external/cwe/cwe-685 + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/call/wrong-number-class-arguments + */ + +import python +import Expressions.CallArgs + +from Call call, ClassObject cls, string too, string should, int limit, FunctionObject init +where +( + too_many_args(call, cls, limit) and too = "too many arguments" and should = "no more than " + or + too_few_args(call, cls, limit) and too = "too few arguments" and should = "no fewer than " +) and init = get_function_or_initializer(cls) +select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init, init.getQualifiedName() diff --git a/python/ql/src/Exceptions/CatchingBaseException.py b/python/ql/src/Exceptions/CatchingBaseException.py new file mode 100644 index 000000000000..b51dc5f4a2e1 --- /dev/null +++ b/python/ql/src/Exceptions/CatchingBaseException.py @@ -0,0 +1,27 @@ + +def call_main_program_implicit_handle_base_exception(): + try: + #application.main calls sys.exit() when done. + application.main() + except Exception as ex: + log(ex) + except: + pass + +def call_main_program_explicit_handle_base_exception(): + try: + #application.main calls sys.exit() when done. + application.main() + except Exception as ex: + log(ex) + except BaseException: + pass + +def call_main_program_fixed(): + try: + #application.main calls sys.exit() when done. + application.main() + except Exception as ex: + log(ex) + except SystemExit: + pass diff --git a/python/ql/src/Exceptions/CatchingBaseException.qhelp b/python/ql/src/Exceptions/CatchingBaseException.qhelp new file mode 100644 index 000000000000..725f9ce465ba --- /dev/null +++ b/python/ql/src/Exceptions/CatchingBaseException.qhelp @@ -0,0 +1,55 @@ + + + +

    +All exception classes in Python derive from BaseException. BaseException has three important subclasses, +Exception from which all errors and normal exceptions derive, KeyboardInterrupt which is raised when the +user interrupts the program from the keyboard and SystemExit which is raised by the sys.exit() function to +terminate the program. +

    + +

    +Since KeyboardInterrupt and SystemExit are special they should not be grouped together with other +Exception classes. +

    + +

    +Catching BaseException, rather than its subclasses may prevent proper handling of +KeyboardInterrupt or SystemExit. It is easy to catch BaseException +accidentally as it is caught implicitly by an empty except: statement. +

    + +
    + + +

    +Handle Exception, KeyboardInterrupt and SystemExit separately. Do not use the plain except: form. +

    + +
    + +

    +In these examples, a function application.main() is called that might raise SystemExit. +In the first two functions, BaseException is caught, but this will discard KeyboardInterrupt. +In the third function, call_main_program_fixed only SystemExit is caught, +leaving KeyboardInterrupt to propagate. +

    + + +

    In these examples KeyboardInterrupt is accidentally ignored.

    + + + +
    + + +
  • Python Language Reference: The try statement, +Exceptions.
  • +
  • M. Lutz, Learning Python, Section 35.3: Exception Design Tips and Gotchas, O'Reilly Media, 2013.
  • +
  • Python Tutorial: Errors and Exceptions.
  • + + +
    +
    diff --git a/python/ql/src/Exceptions/CatchingBaseException.ql b/python/ql/src/Exceptions/CatchingBaseException.ql new file mode 100644 index 000000000000..4d5be501ecfc --- /dev/null +++ b/python/ql/src/Exceptions/CatchingBaseException.ql @@ -0,0 +1,30 @@ +/** + * @name Except block handles 'BaseException' + * @description Handling 'BaseException' means that system exits and keyboard interrupts may be mis-handled. + * @kind problem + * @tags reliability + * readability + * convention + * external/cwe/cwe-396 + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/catch-base-exception + */ + +import python + +predicate doesnt_reraise(ExceptStmt ex) { + ex.getAFlowNode().getBasicBlock().reachesExit() +} + +predicate catches_base_exception(ExceptStmt ex) { + ex.getType().refersTo(theBaseExceptionType()) + or + not exists(ex.getType()) +} + +from ExceptStmt ex +where catches_base_exception(ex) and +doesnt_reraise(ex) +select ex, "Except block directly handles BaseException." diff --git a/python/ql/src/Exceptions/EmptyExcept.py b/python/ql/src/Exceptions/EmptyExcept.py new file mode 100644 index 000000000000..c01c00a05931 --- /dev/null +++ b/python/ql/src/Exceptions/EmptyExcept.py @@ -0,0 +1,6 @@ +# ... +try: + security_manager.drop_privileges() +except SecurityError: + pass +# ... \ No newline at end of file diff --git a/python/ql/src/Exceptions/EmptyExcept.qhelp b/python/ql/src/Exceptions/EmptyExcept.qhelp new file mode 100644 index 000000000000..9b7ef09643fb --- /dev/null +++ b/python/ql/src/Exceptions/EmptyExcept.qhelp @@ -0,0 +1,27 @@ + + + +

    Ignoring exceptions that should be dealt with in some way is almost always a bad idea. +The loss of information can lead to hard to debug errors and incomplete log files. +It is even possible that ignoring an exception can cause a security vulnerability. +An empty except block may be an indication that the programmer intended to +handle the exception but never wrote the code to do so.

    + +
    + +

    Ensure all exceptions are handled correctly.

    + +
    + +

    In this example the program keeps running with the same privileges if it fails to drop to lower +privileges.

    + + +
    + + + + +
    diff --git a/python/ql/src/Exceptions/EmptyExcept.ql b/python/ql/src/Exceptions/EmptyExcept.ql new file mode 100755 index 000000000000..592aec421bfa --- /dev/null +++ b/python/ql/src/Exceptions/EmptyExcept.ql @@ -0,0 +1,106 @@ +/** + * @name Empty except + * @description Except doesn't do anything and has no comment + * @kind problem + * @tags reliability + * maintainability + * external/cwe/cwe-390 + * @problem.severity recommendation + * @sub-severity high + * @precision high + * @id py/empty-except + */ + +import python + +predicate +empty_except(ExceptStmt ex) { + not exists(Stmt s | s = ex.getAStmt() and not s instanceof Pass) +} + +predicate no_else(ExceptStmt ex) { + not exists(ex.getTry().getOrelse()) +} + +predicate no_comment(ExceptStmt ex) { + not exists(Comment c | + c.getLocation().getFile() = ex.getLocation().getFile() and + c.getLocation().getStartLine() >= ex.getLocation().getStartLine() and + c.getLocation().getEndLine() <= ex.getBody().getLastItem().getLocation().getEndLine() + ) +} + +predicate non_local_control_flow(ExceptStmt ex) { + ex.getType().refersTo(theStopIterationType()) +} + +predicate try_has_normal_exit(Try try) { + exists(ControlFlowNode pred, ControlFlowNode succ | + /* Exists a non-exception predecessor, successor pair */ + pred.getASuccessor() = succ and + not pred.getAnExceptionalSuccessor() = succ | + /* Successor is either a normal flow node or a fall-through exit */ + not exists(Scope s | s.getReturnNode() = succ) and + /* Predecessor is in try body and successor is not */ + pred.getNode().getParentNode*() = try.getAStmt() and + not succ.getNode().getParentNode*() = try.getAStmt() + ) +} + +predicate attribute_access(Stmt s) { + s.(ExprStmt).getValue() instanceof Attribute + or + exists(string name | + s.(ExprStmt).getValue().(Call).getFunc().(Name).getId() = name | + name = "getattr" or name = "setattr" or name = "delattr" + ) + or + s.(Delete).getATarget() instanceof Attribute +} + +predicate subscript(Stmt s) { + s.(ExprStmt).getValue() instanceof Subscript + or + s.(Delete).getATarget() instanceof Subscript +} + +predicate encode_decode(Expr ex, ClassObject type) { + exists(string name | + ex.(Call).getFunc().(Attribute).getName() = name | + name = "encode" and type = builtin_object("UnicodeEncodeError") + or + name = "decode" and type = builtin_object("UnicodeDecodeError") + ) +} + +predicate small_handler(ExceptStmt ex, Stmt s, ClassObject type) { + not exists(ex.getTry().getStmt(1)) and + s = ex.getTry().getStmt(0) and + ex.getType().refersTo(type) +} + +/** Holds if this exception handler is sufficiently small in scope to not need a comment + * as to what it is doing. + */ +predicate focussed_handler(ExceptStmt ex) { + exists(Stmt s, ClassObject type | + small_handler(ex, s, type) | + subscript(s) and type.getAnImproperSuperType() = theLookupErrorType() + or + attribute_access(s) and type = theAttributeErrorType() + or + s.(ExprStmt).getValue() instanceof Name and type = theNameErrorType() + or + encode_decode(s.(ExprStmt).getValue(), type) + ) +} + +Try try_return() { + not exists(result.getStmt(1)) and result.getStmt(0) instanceof Return +} + +from ExceptStmt ex +where empty_except(ex) and no_else(ex) and no_comment(ex) and not non_local_control_flow(ex) + and not ex.getTry() = try_return() and try_has_normal_exit(ex.getTry()) and + not focussed_handler(ex) +select ex, "'except' clause does nothing but pass and there is no explanatory comment." diff --git a/python/ql/src/Exceptions/IllegalExceptionHandlerType.py b/python/ql/src/Exceptions/IllegalExceptionHandlerType.py new file mode 100644 index 000000000000..e6b79af0e1d0 --- /dev/null +++ b/python/ql/src/Exceptions/IllegalExceptionHandlerType.py @@ -0,0 +1,7 @@ +def handle_int(): + try: + raise_int() + #This will not cause an exception, but it will be ignored + except int: + print("This will never be printed") + diff --git a/python/ql/src/Exceptions/IllegalExceptionHandlerType.qhelp b/python/ql/src/Exceptions/IllegalExceptionHandlerType.qhelp new file mode 100644 index 000000000000..8788b8800d0b --- /dev/null +++ b/python/ql/src/Exceptions/IllegalExceptionHandlerType.qhelp @@ -0,0 +1,40 @@ + + + +

    If the class specified in an except handler (within a try statement) is +not a legal exception class, then it will never match a raised exception and never be executed +

    + +

    Legal exception classes are:

    +
      +
    • Any old-style classes (Python 2 only)
    • +
    • Any subclass of the builtin class BaseException
    • +
    +

    +However, it recommended that you only use subclasses of the builtin class +Exception (which is itself a subclass of BaseException). +

    + +
    + +

    Ensure that the specified class is the one intended. If it is not then replace it with +the correct one. Otherwise the entire except block can be deleted. +

    + +
    + + + + + + + + +
  • Python Language Reference: Exceptions.
  • +
  • Python Tutorial: Handling Exceptions.
  • + + +
    +
    diff --git a/python/ql/src/Exceptions/IllegalExceptionHandlerType.ql b/python/ql/src/Exceptions/IllegalExceptionHandlerType.ql new file mode 100644 index 000000000000..24a15198f18c --- /dev/null +++ b/python/ql/src/Exceptions/IllegalExceptionHandlerType.ql @@ -0,0 +1,30 @@ +/** + * @name Non-exception in 'except' clause + * @description An exception handler specifying a non-exception type will never handle any exception. + * @kind problem + * @tags reliability + * correctness + * types + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/useless-except + */ + +import python + +from ExceptFlowNode ex, Object t, ClassObject c, ControlFlowNode origin, string what +where ex.handledException(t, c, origin) and +( + exists(ClassObject x | x = t | + not x.isLegalExceptionType() and + not x.failedInference() and + what = "class '" + x.getName() + "'" + ) + or + not t instanceof ClassObject and + what = "instance of '" + c.getName() + "'" +) + +select ex.getNode(), "Non-exception $@ in exception handler which will never match raised exception.", origin, what + diff --git a/python/ql/src/Exceptions/IllegalRaise.py b/python/ql/src/Exceptions/IllegalRaise.py new file mode 100644 index 000000000000..5f50d3db901a --- /dev/null +++ b/python/ql/src/Exceptions/IllegalRaise.py @@ -0,0 +1,5 @@ +#Cannot raise an int, even if we want to +def raise_int(): + #Will raise a TypeError + raise 4 + diff --git a/python/ql/src/Exceptions/IllegalRaise.qhelp b/python/ql/src/Exceptions/IllegalRaise.qhelp new file mode 100644 index 000000000000..da7bcb75afec --- /dev/null +++ b/python/ql/src/Exceptions/IllegalRaise.qhelp @@ -0,0 +1,38 @@ + + + +

    If the object raised is not a legal Exception class or an instance of one, then +a TypeError will be raised instead.

    + +

    Legal exception classes are:

    +
      +
    • Any old-style classes (Python 2 only)
    • +
    • Any subclass of the builtin class BaseException
    • +
    +

    +However, it recommended that you only use subclasses of the builtin class +Exception (which is itself a subclass of BaseException). +

    + +
    + +

    Change the expression in the raise statement to be a legal exception.

    + + +
    + + + + + + + + +
  • Python Language Reference: Exceptions.
  • +
  • Python Tutorial: Handling Exceptions.
  • + + +
    +
    diff --git a/python/ql/src/Exceptions/IllegalRaise.ql b/python/ql/src/Exceptions/IllegalRaise.ql new file mode 100644 index 000000000000..673289b9e975 --- /dev/null +++ b/python/ql/src/Exceptions/IllegalRaise.ql @@ -0,0 +1,21 @@ +/** + * @name Illegal raise + * @description Raising a non-exception object or type will result in a TypeError being raised instead. + * @kind problem + * @tags reliability + * correctness + * types + * @problem.severity error + * @sub-severity high + * @precision very-high + * @id py/illegal-raise + */ + +import python +import Raising +import Exceptions.NotImplemented + +from Raise r, ClassObject t +where type_or_typeof(r, t, _) and not t.isLegalExceptionType() and not t.failedInference() and not use_of_not_implemented_in_raise(r, _) +select r, "Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead." + diff --git a/python/ql/src/Exceptions/IncorrectExceptOrder.py b/python/ql/src/Exceptions/IncorrectExceptOrder.py new file mode 100644 index 000000000000..15ad395b905b --- /dev/null +++ b/python/ql/src/Exceptions/IncorrectExceptOrder.py @@ -0,0 +1,10 @@ + + +def incorrect_except_order(val): + try: + val.attr + except Exception: + print ("Exception") + except AttributeError: + print ("AttributeError") + diff --git a/python/ql/src/Exceptions/IncorrectExceptOrder.qhelp b/python/ql/src/Exceptions/IncorrectExceptOrder.qhelp new file mode 100644 index 000000000000..d2854af6ca60 --- /dev/null +++ b/python/ql/src/Exceptions/IncorrectExceptOrder.qhelp @@ -0,0 +1,45 @@ + + + +

    When handling an exception, Python searches the except blocks in source code order +until it finds a matching except block for the exception. +An except block, except E:, specifies a class E and will match any +exception that is an instance of E. +

    +

    +If a more general except block precedes a more specific except block, +then the more general block is always executed and the more specific block is never executed. +An except block, except A:, is more general than another except block, except B:, +if A is a super class of B. +

    +

    +For example: +except Exception: is more general than except Error: as Exception +is a super class of Error. +

    + +
    + + +

    Reorganize the except blocks so that the more specific except +is defined first. Alternatively, if the more specific except block is +no longer required then it should be deleted.

    + +
    + +

    In this example the except Exception: will handle AttributeError preventing the +subsequent handler from ever executing.

    + + + +
    + + +
  • Python Language Reference: The try statement, +Exceptions.
  • + + +
    +
    diff --git a/python/ql/src/Exceptions/IncorrectExceptOrder.ql b/python/ql/src/Exceptions/IncorrectExceptOrder.ql new file mode 100644 index 000000000000..566f3c68175e --- /dev/null +++ b/python/ql/src/Exceptions/IncorrectExceptOrder.ql @@ -0,0 +1,34 @@ +/** + * @name Unreachable 'except' block + * @description Handling general exceptions before specific exceptions means that the specific + * handlers are never executed. + * @kind problem + * @tags reliability + * maintainability + * external/cwe/cwe-561 + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/unreachable-except + */ + +import python + +predicate incorrect_except_order(ExceptStmt ex1, ClassObject cls1, ExceptStmt ex2, ClassObject cls2) { + exists(int i, int j, Try t | + ex1 = t.getHandler(i) and + ex2 = t.getHandler(j) and i < j and + cls1 = except_class(ex1) and + cls2 = except_class(ex2) and + cls1 = cls2.getASuperType() + ) +} + +ClassObject except_class(ExceptStmt ex) { + ex.getType().refersTo(result) +} + +from ExceptStmt ex1, ClassObject cls1, ExceptStmt ex2, ClassObject cls2 +where incorrect_except_order(ex1, cls1, ex2, cls2) +select ex2, "Except block for $@ is unreachable; the more general $@ for $@ will always be executed in preference.", + cls2, cls2.getName(), ex1, "except block", cls1, cls1.getName() diff --git a/python/ql/src/Exceptions/NotImplemented.py b/python/ql/src/Exceptions/NotImplemented.py new file mode 100644 index 000000000000..53b3189f0995 --- /dev/null +++ b/python/ql/src/Exceptions/NotImplemented.py @@ -0,0 +1,9 @@ + +class Abstract(object): + + def wrong(self): + # Will raise a TypeError + raise NotImplemented() + + def right(self): + raise NotImplementedError() diff --git a/python/ql/src/Exceptions/NotImplemented.qll b/python/ql/src/Exceptions/NotImplemented.qll new file mode 100644 index 000000000000..016204a7cd1d --- /dev/null +++ b/python/ql/src/Exceptions/NotImplemented.qll @@ -0,0 +1,11 @@ + +import python + +/** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */ +predicate use_of_not_implemented_in_raise(Raise raise, Expr notimpl) { + notimpl.refersTo(theNotImplementedObject()) and + ( + notimpl = raise.getException() or + notimpl = raise.getException().(Call).getFunc() + ) +} diff --git a/python/ql/src/Exceptions/NotImplementedIsNotAnException.qhelp b/python/ql/src/Exceptions/NotImplementedIsNotAnException.qhelp new file mode 100644 index 000000000000..3bf09bbfab07 --- /dev/null +++ b/python/ql/src/Exceptions/NotImplementedIsNotAnException.qhelp @@ -0,0 +1,41 @@ + + + + +

    NotImplemented is not an Exception, but is often mistakenly used in place of NotImplementedError. +Executing raise NotImplemented or raise NotImplemented() will raise a TypeError. +When raise NotImplemented is used to mark code that is genuinely never called, this mistake is benign. + +However, should it be called, then a TypeError will be raised rather than the expected NotImplemented, +which might make debugging the issue difficult. +

    + +

    The correct use of NotImplemented is to implement binary operators. +Code that is not intended to be called should raise NotImplementedError.

    + +
    + +

    Replace uses of NotImplemented with NotImplementedError.

    +
    + + +

    +In the example below, the method wrong will incorrectly raise a TypeError when called. +The method right will raise a NotImplementedError. +

    + + + + +
    + + + +
  • Python Language Reference: The NotImplementedError exception.
  • +
  • Python Language Reference: Emulating numeric types.
  • + +
    + +
    diff --git a/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql b/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql new file mode 100644 index 000000000000..89f1bb045682 --- /dev/null +++ b/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql @@ -0,0 +1,19 @@ +/** + * @name NotImplemented is not an Exception + * @description Using 'NotImplemented' as an exception will result in a type error. + * @kind problem + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/raise-not-implemented + * @tags reliability + * maintainability + */ + +import python +import Exceptions.NotImplemented + +from Expr notimpl +where use_of_not_implemented_in_raise(_, notimpl) + +select notimpl, "NotImplemented is not an Exception. Did you mean NotImplementedError?" diff --git a/python/ql/src/Exceptions/Raising.qll b/python/ql/src/Exceptions/Raising.qll new file mode 100644 index 000000000000..b820bfa24e9c --- /dev/null +++ b/python/ql/src/Exceptions/Raising.qll @@ -0,0 +1,14 @@ +import python + +/** Whether the raise statement 'r' raises 'type' from origin 'orig' */ +predicate type_or_typeof(Raise r, ClassObject type, AstNode orig) { + exists(Expr exception | + exception = r.getRaised() | + exception.refersTo(type, _, orig) + or + not exists(ClassObject exc_type | exception.refersTo(exc_type)) and + not type = theTypeType() and // First value is an unknown exception type + exception.refersTo(_, type, orig) + ) + +} diff --git a/python/ql/src/Exceptions/RaisingTuple.py b/python/ql/src/Exceptions/RaisingTuple.py new file mode 100644 index 000000000000..50b7e81f82ae --- /dev/null +++ b/python/ql/src/Exceptions/RaisingTuple.py @@ -0,0 +1,5 @@ + + +def raise_tuple(): + ex = Exception, "Important diagnostic information" + raise ex diff --git a/python/ql/src/Exceptions/RaisingTuple.qhelp b/python/ql/src/Exceptions/RaisingTuple.qhelp new file mode 100644 index 000000000000..4f47d3ee302e --- /dev/null +++ b/python/ql/src/Exceptions/RaisingTuple.qhelp @@ -0,0 +1,47 @@ + + + +

    In Python 2, if a tuple is raised then all elements but the first are ignored and only the first part is raised. +If the first element is itself a tuple, then the first element of that is used and so on. +This unlikely to be the intended effect and will most likely indicate some sort of error.

    + +

    It is important to note that the exception in raise Exception, message is not a tuple, whereas the exception +in ex = Exception, message; raise ex is a tuple.

    + +

    +In Python 3, raising a tuple is an error. +

    + + +
    + + +

    Given that all but the first element of the tuple is ignored, +the tuple should be replaced with its first element in order to +improve the clarity of the code. If the subsequent parts of the tuple +were intended to form the message, then they should be passed as an argument +when creating the exception. +

    + + + +
    + + +

    In the following example the intended error message is mistakenly used to form a tuple.

    + +

    This can be fixed, either by using the message to create the exception or using the message in the raise +statement, as shown below.

    + + +
    + + +
  • Python Language Reference: Exceptions.
  • +
  • Python Tutorial: Handling Exceptions.
  • + + +
    +
    diff --git a/python/ql/src/Exceptions/RaisingTuple.ql b/python/ql/src/Exceptions/RaisingTuple.ql new file mode 100644 index 000000000000..8bf5c7705b5e --- /dev/null +++ b/python/ql/src/Exceptions/RaisingTuple.ql @@ -0,0 +1,18 @@ +/** + * @name Raising a tuple + * @description Raising a tuple will result in all but the first element being discarded + * @kind problem + * @tags maintainability + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/raises-tuple + */ + +import python + +from Raise r, AstNode origin +where r.getException().refersTo(_, theTupleType(), origin) and +major_version() = 2 /* Raising a tuple is a type error in Python 3, so is handled by the IllegalRaise query. */ + +select r, "Raising $@ will result in the first element (recursively) being raised and all other elements being discarded.", origin, "a tuple" \ No newline at end of file diff --git a/python/ql/src/Exceptions/RaisingTuple2.py b/python/ql/src/Exceptions/RaisingTuple2.py new file mode 100644 index 000000000000..0d8b85657ff6 --- /dev/null +++ b/python/ql/src/Exceptions/RaisingTuple2.py @@ -0,0 +1,9 @@ + + +def fixed_raise_tuple1(): + ex = Exception("Important diagnostic information") + raise ex + + +def fixed_raise_tuple2(): + raise Exception, "Important diagnostic information" diff --git a/python/ql/src/Exceptions/UnguardedNextInGenerator.qhelp b/python/ql/src/Exceptions/UnguardedNextInGenerator.qhelp new file mode 100644 index 000000000000..94474906bbc4 --- /dev/null +++ b/python/ql/src/Exceptions/UnguardedNextInGenerator.qhelp @@ -0,0 +1,51 @@ + + + +

    +The function next() will raise a StopIteration exception +if the underlying iterator is exhausted. +Normally this is fine, but in a generator may cause problems. +Since the StopIteration is an exception it will be propagated out of the generator +causing termination of the generator. This is unlikely to be the expected behavior and may mask +errors. +

    + +

    +This problem is considered sufficiently serious that PEP 479 +has been accepted to modify the handling of StopIteration in generators. Consequently, code that does not handle +StopIteration properly is likely to fail in future versions of Python. +

    + +
    + +

    +Each call to next() should be wrapped in a try-except to explicitly +handle StopIteration exceptions. +

    + +
    + +

    +In the following example, an empty file part way through iteration will silently truncate the output as +the StopIteration exception propagates to the top level. +

    + + + +

    +In the following example StopIteration exception is explicitly handled, +allowing all the files to be processed. +

    + + + +
    + + +
  • Python PEP index: PEP 479.
  • + + +
    +
    diff --git a/python/ql/src/Exceptions/UnguardedNextInGenerator.ql b/python/ql/src/Exceptions/UnguardedNextInGenerator.ql new file mode 100755 index 000000000000..ca3683f05603 --- /dev/null +++ b/python/ql/src/Exceptions/UnguardedNextInGenerator.ql @@ -0,0 +1,61 @@ +/** + * @name Unguarded next in generator + * @description Calling next() in a generator may cause unintended early termination of an iteration. + * @kind problem + * @tags maintainability + * portability + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/unguarded-next-in-generator + */ + +import python + +FunctionObject iter() { + result = builtin_object("iter") +} + +FunctionObject next() { + result = builtin_object("next") +} + +predicate call_to_iter(CallNode call, EssaVariable sequence) { + sequence.getAUse() = iter().getArgumentForCall(call, 0) +} + +predicate call_to_next(CallNode call, ControlFlowNode iter) { + iter = next().getArgumentForCall(call, 0) +} + +predicate guarded_not_empty_sequence(EssaVariable sequence) { + sequence.getDefinition() instanceof EssaEdgeRefinement +} + +/** The pattern `next(iter(x))` is often used where `x` is known not be empty. Check for that. */ +predicate iter_not_exhausted(EssaVariable iterator) { + exists(EssaVariable sequence | + call_to_iter(iterator.getDefinition().(AssignmentDefinition).getValue(), sequence) and + guarded_not_empty_sequence(sequence) + ) +} + +predicate stop_iteration_handled(CallNode call) { + exists(Try t | + t.containsInScope(call.getNode()) and + t.getAHandler().getType().refersTo(theStopIterationType()) + ) +} + +from CallNode call +where call_to_next(call, _) and +not exists(EssaVariable iterator | + call_to_next(call, iterator.getAUse()) and + iter_not_exhausted(iterator) +) and +call.getNode().getScope().(Function).isGenerator() and +not exists(Comp comp | comp.contains(call.getNode())) and +not stop_iteration_handled(call) + +select call, "Call to next() in a generator" + diff --git a/python/ql/src/Exceptions/UnguardedNextInGeneratorBad.py b/python/ql/src/Exceptions/UnguardedNextInGeneratorBad.py new file mode 100644 index 000000000000..550ae35e71a1 --- /dev/null +++ b/python/ql/src/Exceptions/UnguardedNextInGeneratorBad.py @@ -0,0 +1,19 @@ + +test_files = [ + ["header1", "text10", "text11", "text12"], + ["header2", "text20", "text21", "text22"], + [], + ["header4", "text40", "text41", "text42"], +] + +def separate_headers(files): + for file in files: + lines = iter(file) + header = next(lines) # Will raise StopIteration if lines is exhausted + body = [ l for l in lines ] + yield header, body + +def process_files(files): + for header, body in separate_headers(files): + print(format_page(header, body)) + diff --git a/python/ql/src/Exceptions/UnguardedNextInGeneratorGood.py b/python/ql/src/Exceptions/UnguardedNextInGeneratorGood.py new file mode 100644 index 000000000000..23121888558a --- /dev/null +++ b/python/ql/src/Exceptions/UnguardedNextInGeneratorGood.py @@ -0,0 +1,11 @@ + +def separate_headers(files): + for file in files: + lines = iter(file) + try: + header = next(lines) # Will raise StopIteration if lines is exhausted + except StopIteration: + #Empty file -- Just ignore + continue + body = [ l for l in lines ] + yield header, body diff --git a/python/ql/src/Expressions/CallArgs.qll b/python/ql/src/Expressions/CallArgs.qll new file mode 100644 index 000000000000..fc61f38a826f --- /dev/null +++ b/python/ql/src/Expressions/CallArgs.qll @@ -0,0 +1,130 @@ +import python + +import Testing.Mox + +private int varargs_length(Call call) { + not exists(call.getStarargs()) and result = 0 + or + exists(TupleObject t | + call.getStarargs().refersTo(t) | + result = t.getLength() + ) + or + result = count(call.getStarargs().(List).getAnElt()) +} + +/** Gets a keyword argument that is not a keyword-only parameter. */ +private Keyword not_keyword_only_arg(Call call, FunctionObject func) { + func.getACall().getNode() = call and + result = call.getAKeyword() and + not func.getFunction().getAKeywordOnlyArg().getId() = result.getArg() +} + +/** Gets the count of arguments that are passed as positional parameters even if they + * are named in the call. + * This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg + * plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs). + */ + +private int positional_arg_count_for_call(Call call, Object callable) { + call = get_a_call(callable).getNode() and + exists(int positional_keywords | + exists(FunctionObject func | func = get_function_or_initializer(callable) | + not func.getFunction().hasKwArg() and + positional_keywords = count(not_keyword_only_arg(call, func)) + or + func.getFunction().hasKwArg() and positional_keywords = 0 + ) + | + result = count(call.getAnArg()) + varargs_length(call) + positional_keywords + ) +} + +int arg_count(Call call) { + result = count(call.getAnArg()) + varargs_length(call) + count(call.getAKeyword()) +} + +/* Gets a call corresponding to the given class or function*/ +private ControlFlowNode get_a_call(Object callable) { + result = callable.(ClassObject).getACall() + or + result = callable.(FunctionObject).getACall() +} + +/* Gets the function object corresponding to the given class or function*/ +FunctionObject get_function_or_initializer(Object func_or_cls) { + result = func_or_cls.(FunctionObject) + or + result = func_or_cls.(ClassObject).declaredAttribute("__init__") +} + + +/**Whether there is an illegally named parameter called `name` in the `call` to `func` */ +predicate illegally_named_parameter(Call call, Object func, string name) { + not func.isC() and + name = call.getANamedArgumentName() and + call.getAFlowNode() = get_a_call(func) and + not get_function_or_initializer(func).isLegalArgumentName(name) +} + +/**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */ +predicate too_few_args(Call call, Object callable, int limit) { + // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' + not illegally_named_parameter(call, callable, _) and + not exists(call.getStarargs()) and not exists(call.getKwargs()) and + arg_count(call) < limit and + exists(FunctionObject func | func = get_function_or_initializer(callable) | + call = func.getAFunctionCall().getNode() and limit = func.minParameters() and + /* The combination of misuse of `mox.Mox().StubOutWithMock()` + * and a bug in mox's implementation of methods results in having to + * pass 1 too few arguments to the mocked function. + */ + not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod()) + or + call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1 + or + callable instanceof ClassObject and + call.getAFlowNode() = get_a_call(callable) and limit = func.minParameters() - 1 + ) +} + +/**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */ +predicate too_many_args(Call call, Object callable, int limit) { + // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' + not illegally_named_parameter(call, callable, _) and + exists(FunctionObject func | + func = get_function_or_initializer(callable) and + not func.getFunction().hasVarArg() and limit >= 0 + | + call = func.getAFunctionCall().getNode() and limit = func.maxParameters() + or + call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1 + or + callable instanceof ClassObject and + call.getAFlowNode() = get_a_call(callable) and limit = func.maxParameters() - 1 + ) and + positional_arg_count_for_call(call, callable) > limit +} + +/** Holds if `call` has too many or too few arguments for `func` */ +predicate wrong_args(Call call, FunctionObject func, int limit, string too) { + too_few_args(call, func, limit) and too = "too few" + or + too_many_args(call, func, limit) and too = "too many" +} + +/** Holds if `call` has correct number of arguments for `func`. + * Implies nothing about whether `call` could call `func`. + */ + bindingset[call, func] +predicate correct_args_if_called_as_method(Call call, FunctionObject func) { + arg_count(call)+1 >= func.minParameters() + and + arg_count(call) < func.maxParameters() +} + +/** Holds if `call` is a call to `overriding`, which overrides `func`. */ +predicate overridden_call(FunctionObject func, FunctionObject overriding, Call call) { + overriding.overrides(func) and + overriding.getACall().getNode() = call +} diff --git a/python/ql/src/Expressions/CallToSuperWrongClass.py b/python/ql/src/Expressions/CallToSuperWrongClass.py new file mode 100644 index 000000000000..dee56fb7aec5 --- /dev/null +++ b/python/ql/src/Expressions/CallToSuperWrongClass.py @@ -0,0 +1,34 @@ + + +class Vehicle(object): + pass + +class Car(Vehicle): + + def __init__(self): + #This is OK provided that Car is not subclassed. + super(Vehicle, self).__init__() + self.car_init() + +class StatusSymbol(object): + + def __init__(self): + super(StatusSymbol, self).__init__() + self.show_off() + +class SportsCar(Car, StatusSymbol): + + def __init__(self): + #This will not call StatusSymbol.__init__() + super(SportsCar, self).__init__() + self.sports_car_init() + + +#Fix Car by passing Car to super(). +#SportsCar does not need to be changed. +class Car(Car, Vehicle): + + def __init__(self): + super(Car, self).__init__() + self.car_init() + diff --git a/python/ql/src/Expressions/CallToSuperWrongClass.qhelp b/python/ql/src/Expressions/CallToSuperWrongClass.qhelp new file mode 100644 index 000000000000..dc88b1bea88c --- /dev/null +++ b/python/ql/src/Expressions/CallToSuperWrongClass.qhelp @@ -0,0 +1,45 @@ + + + +

    +The super class should be called with the enclosing class as its first argument and self as its second argument. +

    +

    +Passing a different class may work correctly, provided the class passed is a super class of the enclosing class and the enclosing class +does not define an __init__ method. +However, this may result in incorrect object initialization if the enclosing class is later subclassed using multiple inheritance. +

    + + +
    + + +

    + Ensure that the first argument to super() is the enclosing class. +

    + + +
    + +

    +In this example the call to super(Vehicle, self) in Car.__init__ is incorrect as it +passes Vehicle rather than Car as the first argument to super. +As a result, super(SportsCar, self).__init__() in the SportsCar.__init__ method will not call +all __init__() methods because the call to super(Vehicle, self).__init__() +skips StatusSymbol.__init__(). +

    + + + + +
    + + +
  • Python Standard Library: super.
  • +
  • Artima Developer: Things to Know About Python Super.
  • + + +
    +
    diff --git a/python/ql/src/Expressions/CallToSuperWrongClass.ql b/python/ql/src/Expressions/CallToSuperWrongClass.ql new file mode 100644 index 000000000000..a42cbcefe4be --- /dev/null +++ b/python/ql/src/Expressions/CallToSuperWrongClass.ql @@ -0,0 +1,29 @@ +/** + * @name First argument to super() is not enclosing class + * @description Calling super with something other than the enclosing class may cause incorrect object initialization. + * @kind problem + * @tags reliability + * maintainability + * convention + * external/cwe/cwe-687 + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/super-not-enclosing-class + */ + +import python + +from CallNode call_to_super, string name +where +exists(GlobalVariable gv, ControlFlowNode cn | + call_to_super = theSuperType().getACall() and + gv.getId() = "super" and + cn = call_to_super.getArg(0) and + name = call_to_super.getScope().getScope().(Class).getName() and + exists(ClassObject other | + cn.refersTo(other) and + not other.getPyClass().getName() = name + ) +) +select call_to_super.getNode(), "First argument to super() should be " + name + "." diff --git a/python/ql/src/Expressions/CompareConstants.py b/python/ql/src/Expressions/CompareConstants.py new file mode 100644 index 000000000000..7345433edec4 --- /dev/null +++ b/python/ql/src/Expressions/CompareConstants.py @@ -0,0 +1,6 @@ + +#Interoperate with very old versions of Python (pre 2.3) +try: + True +except NameError: + __builtins__.True = 1==1 diff --git a/python/ql/src/Expressions/CompareConstants.qhelp b/python/ql/src/Expressions/CompareConstants.qhelp new file mode 100644 index 000000000000..3dcbd1cc4ab1 --- /dev/null +++ b/python/ql/src/Expressions/CompareConstants.qhelp @@ -0,0 +1,35 @@ + + + + + +

    When two constants are compared it is typically an +indication of a mistake, since the Boolean value of the comparison +will always be the same. In very old code this may be used to initialize +True and False.

    + +
    + + +

    It is never good practice to compare a value with itself. If the constant +behavior is indeed required, use the Boolean literals True or +False, rather than encoding them obscurely as 1 == 1 +or similar. If there is a mistake, ascertain the desired behavior and correct it. +

    + +
    + + +

    In this example, old code uses 1==1 to initialize __builtins__.True. +This code has been unnecessary on all versions of Python released since 2003 and can be deleted. +

    + +
    + + +
  • Python Language Reference: Comparisons.
  • + +
    +
    diff --git a/python/ql/src/Expressions/CompareConstants.ql b/python/ql/src/Expressions/CompareConstants.ql new file mode 100644 index 000000000000..2a66a952c5e0 --- /dev/null +++ b/python/ql/src/Expressions/CompareConstants.ql @@ -0,0 +1,21 @@ +/** + * @name Comparison of constants + * @description Comparison of constants is always constant, but is harder to read than a simple constant. + * @kind problem + * @tags maintainability + * useless-code + * external/cwe/cwe-570 + * external/cwe/cwe-571 + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/comparison-of-constants + */ + +import python + +from Compare comparison, Expr left, Expr right +where + comparison.compares(left, _, right) and left.isConstant() and right.isConstant() and + not exists(Assert a | a.getTest() = comparison) +select comparison, "Comparison of constants; use 'True' or 'False' instead." diff --git a/python/ql/src/Expressions/CompareIdenticalValues.py b/python/ql/src/Expressions/CompareIdenticalValues.py new file mode 100644 index 000000000000..8d115c99c106 --- /dev/null +++ b/python/ql/src/Expressions/CompareIdenticalValues.py @@ -0,0 +1,8 @@ + +#Using 'x == x' to check that 'x' is not a float('nan'). +def is_normal(f): + return not cmath.isinf(f) and f == f + +#Improved version; intention is explicit. +def is_normal(f): + return not cmath.isinf(f) and not cmath.isnan(f) \ No newline at end of file diff --git a/python/ql/src/Expressions/CompareIdenticalValues.qhelp b/python/ql/src/Expressions/CompareIdenticalValues.qhelp new file mode 100644 index 000000000000..5c8c5371ed37 --- /dev/null +++ b/python/ql/src/Expressions/CompareIdenticalValues.qhelp @@ -0,0 +1,38 @@ + + + + + +

    When two identical expressions are compared it is typically an +indication of a mistake, since the Boolean value of the comparison +will always be the same, unless the value is the floating point value float('nan'). +

    + +
    + + +

    It is not good practice to compare a value with itself, as it makes the code hard to read +and can hide errors with classes that do not correctly implement equality. +If testing whether a floating-point value is not-a-number, then use math.isnan(). +If the value may be a complex number, then use cmath.isnan() instead. +

    + +
    + + +

    In this example f == f is used to check for float('nan'). +This makes the code difficult to understand as the reader may not be immediately familiar with this pattern. +

    + +
    + + +
  • Python Language Reference: Comparisons.
  • +
  • Python Library Reference: math.isnan().
  • +
  • Python Library Reference: cmath.isnan().
  • + + +
    +
    diff --git a/python/ql/src/Expressions/CompareIdenticalValues.ql b/python/ql/src/Expressions/CompareIdenticalValues.ql new file mode 100644 index 000000000000..c950d3ebb2ee --- /dev/null +++ b/python/ql/src/Expressions/CompareIdenticalValues.ql @@ -0,0 +1,22 @@ +/** + * @name Comparison of identical values + * @description Comparison of identical values, the intent of which is unclear. + * @kind problem + * @tags reliability + * correctness + * readability + * convention + * external/cwe/cwe-570 + * external/cwe/cwe-571 + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/comparison-of-identical-expressions + */ + +import python +import Expressions.RedundantComparison + +from RedundantComparison comparison +where not comparison.isConstant() and not comparison.maybeMissingSelf() +select comparison, "Comparison of identical values; use cmath.isnan() if testing for not-a-number." diff --git a/python/ql/src/Expressions/CompareIdenticalValuesMissingSelf.py b/python/ql/src/Expressions/CompareIdenticalValuesMissingSelf.py new file mode 100644 index 000000000000..6b4da941e0e3 --- /dev/null +++ b/python/ql/src/Expressions/CompareIdenticalValuesMissingSelf.py @@ -0,0 +1,20 @@ + +class Customer: + + def __init__(self, data): + self.data = data + + def check_data(self, data): + if data != data: # Forgotten 'self' + raise Exception("Invalid data!") + +#Fixed version + +class Customer: + + def __init__(self, data): + self.data = data + + def check_data(self, data): + if self.data != data: + raise Exception("Invalid data!") diff --git a/python/ql/src/Expressions/CompareIdenticalValuesMissingSelf.qhelp b/python/ql/src/Expressions/CompareIdenticalValuesMissingSelf.qhelp new file mode 100644 index 000000000000..36ae280a3b81 --- /dev/null +++ b/python/ql/src/Expressions/CompareIdenticalValuesMissingSelf.qhelp @@ -0,0 +1,32 @@ + + + + + +

    When two identical expressions are compared it is typically an +indication of a mistake, since the Boolean value of the comparison +will always be the same. Often, it can indicate that self has +been omitted.

    + +
    + + +

    It is never good practice to compare a value with itself. +If self has been omitted, then insert it. If the constant +behavior is indeed required, use the Boolean literals True or +False, rather than encoding them obscurely as x == x +or similar.

    + +
    + + + + + + +
  • Python Language Reference: Comparisons.
  • + +
    +
    diff --git a/python/ql/src/Expressions/CompareIdenticalValuesMissingSelf.ql b/python/ql/src/Expressions/CompareIdenticalValuesMissingSelf.ql new file mode 100644 index 000000000000..9d618c2dbb14 --- /dev/null +++ b/python/ql/src/Expressions/CompareIdenticalValuesMissingSelf.ql @@ -0,0 +1,21 @@ +/** + * @name Maybe missing 'self' in comparison + * @description Comparison of identical values, the intent of which is unclear. + * @kind problem + * @tags reliability + * maintainability + * external/cwe/cwe-570 + * external/cwe/cwe-571 + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/comparison-missing-self + */ + +import python +import Expressions.RedundantComparison + +from RedundantComparison comparison +where + comparison.maybeMissingSelf() +select comparison, "Comparison of identical values; may be missing 'self'." diff --git a/python/ql/src/Expressions/Comparisons/UselessComparisonTest.py b/python/ql/src/Expressions/Comparisons/UselessComparisonTest.py new file mode 100644 index 000000000000..da0f09aca457 --- /dev/null +++ b/python/ql/src/Expressions/Comparisons/UselessComparisonTest.py @@ -0,0 +1,15 @@ + class KeySorter: + + def __init__(self, obj): + self.obj = obj + + def __lt__(self, other): + return self._compare(self.obj, other.obj) < 0 + + def _compare(self, obj1, obj2): + if obj1 < obj2: + return -1 + elif obj1 < obj2: + return 1 + else: + return 0 diff --git a/python/ql/src/Expressions/Comparisons/UselessComparisonTest.qhelp b/python/ql/src/Expressions/Comparisons/UselessComparisonTest.qhelp new file mode 100644 index 000000000000..7d1b1bd72792 --- /dev/null +++ b/python/ql/src/Expressions/Comparisons/UselessComparisonTest.qhelp @@ -0,0 +1,35 @@ + + + + + + +

    The result of certain comparisons can sometimes be inferred from their context and the results of other +comparisons. This can be an indication of faulty logic and may result in dead +code or infinite loops if, for example, a loop condition never changes its value. +

    + +
    + +

    Inspect the code to check whether the logic is correct, and consider +simplifying the logical expression. +

    + +
    + +

    In the following (real world) example the test obj1 < obj2 is repeated and thus the +second test will always be false, and the function _compare will only ever return 0 or -1. +

    + + + +
    + + + +
  • Python Language Reference: Comparisons.
  • + +
    +
    diff --git a/python/ql/src/Expressions/Comparisons/UselessComparisonTest.ql b/python/ql/src/Expressions/Comparisons/UselessComparisonTest.ql new file mode 100644 index 000000000000..35277300e25c --- /dev/null +++ b/python/ql/src/Expressions/Comparisons/UselessComparisonTest.ql @@ -0,0 +1,42 @@ +/** + * @name Redundant comparison + * @description The result of a comparison is implied by a previous comparison. + * @kind problem + * @tags useless-code + * external/cwe/cwe-561 + * external/cwe/cwe-570 + * external/cwe/cwe-571 + * @problem.severity warning + * @sub-severity high + * @precision high + * @id py/redundant-comparison + */ + +import python +import semmle.python.Comparisons + + +/** A test is useless if for every block that it controls there is another test that is at least as + * strict and also controls that block. + */ +private predicate useless_test(Comparison comp, ComparisonControlBlock controls, boolean isTrue) { + controls.impliesThat(comp.getBasicBlock(), comp, isTrue) and + /* Exclude complex comparisons of form `a < x < y`, as we do not (yet) have perfect flow control for those */ + not exists(controls.getTest().getNode().(Compare).getOp(1)) +} + +private predicate useless_test_ast(AstNode comp, AstNode previous, boolean isTrue) { + forex(Comparison compnode, ConditionBlock block| + compnode.getNode() = comp and + block.getLastNode().getNode() = previous + | + useless_test(compnode, block, isTrue) + ) +} + +from Expr test, Expr other, boolean isTrue +where +useless_test_ast(test, other, isTrue) and not useless_test_ast(test.getAChildNode+(), other, _) + + +select test, "Test is always " + isTrue + ", because of $@", other, "this condition" diff --git a/python/ql/src/Expressions/ContainsNonContainer.py b/python/ql/src/Expressions/ContainsNonContainer.py new file mode 100644 index 000000000000..69e556039c92 --- /dev/null +++ b/python/ql/src/Expressions/ContainsNonContainer.py @@ -0,0 +1,9 @@ +class NotAContainer(object): + + def __init__(self, *items): + self.items = items + +def main(): + cont = NotAContainer(1, 2, 3) + if 2 in cont: + print("2 in container") diff --git a/python/ql/src/Expressions/ContainsNonContainer.qhelp b/python/ql/src/Expressions/ContainsNonContainer.qhelp new file mode 100644 index 000000000000..6a2709390a35 --- /dev/null +++ b/python/ql/src/Expressions/ContainsNonContainer.qhelp @@ -0,0 +1,42 @@ + + + +

    A membership test, that is a binary expression with +in or not in as the operator, expects that the +expression to the right of the operator will be a container.

    +

    As well as standard containers such as list, tuple, +dict or set, +a container can be an instance of any class that has the __contains__, +__iter__ or __getitem__ method. + +

    + +

    +Ensure that the right hand side of the expression is a container, or add a guard +clause for other cases. +For example, if the right side may be a container or None then change +if x in seq: to if seq is not None and x in seq: +

    + +
    + +

    In this example the NotAContainer class has no __contains__, +__iter__ or __getitem__ method. +Consequently, when the line if 2 in cont: is executed a TypeError +will be raised. Adding a __getitem__ method to the +NotAContainer class would solve the problem. +

    + + + +
    + + +
  • Python: Membership test details.
  • +
  • Python: The __contains__ method.
  • + + +
    +
    diff --git a/python/ql/src/Expressions/ContainsNonContainer.ql b/python/ql/src/Expressions/ContainsNonContainer.ql new file mode 100644 index 000000000000..653574d62b1a --- /dev/null +++ b/python/ql/src/Expressions/ContainsNonContainer.ql @@ -0,0 +1,30 @@ +/** + * @name Membership test with a non-container + * @description A membership test, such as 'item in sequence', with a non-container on the right hand side will raise a 'TypeError'. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity high + * @precision high + * @id py/member-test-non-container + */ + +import python + +predicate rhs_in_expr(ControlFlowNode rhs, Compare cmp) { + exists(Cmpop op, int i | cmp.getOp(i) = op and cmp.getComparator(i) = rhs.getNode() | + op instanceof In or op instanceof NotIn + ) +} + +from ControlFlowNode non_seq, Compare cmp, ClassObject cls, ControlFlowNode origin +where rhs_in_expr(non_seq, cmp) and +non_seq.refersTo(_, cls, origin) and +not cls.failedInference() and +not cls.hasAttribute("__contains__") and +not cls.hasAttribute("__iter__") and +not cls.hasAttribute("__getitem__") and +not cls = theNoneType() + +select cmp, "This test may raise an Exception as the $@ may be of non-container class $@.", origin, "target", cls, cls.getName() \ No newline at end of file diff --git a/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.py b/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.py new file mode 100644 index 000000000000..14804d313005 --- /dev/null +++ b/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.py @@ -0,0 +1,2 @@ +dictionary = {1:"a", 2:"b", 2:"c"} +print dictionary[2] \ No newline at end of file diff --git a/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.qhelp b/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.qhelp new file mode 100644 index 000000000000..19c4df9a5581 --- /dev/null +++ b/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.qhelp @@ -0,0 +1,28 @@ + + + +

    Dictionary literals are constructed in the order given in the source. +This means that if a key is duplicated the second key-value pair will overwrite +the first as a dictionary can only have one value per key. +

    + +
    + +

    Check for typos to ensure that the keys are supposed to be the same. +If they are then decide which value is wanted and delete the other one.

    + +
    + +

    This example will output "c" because the mapping between 2 and "b" is overwritten by the +mapping from 2 to "c". The programmer may have meant to map 3 to "c" instead.

    + + +
    + + +
  • Python: Dictionary literals.
  • + +
    +
    diff --git a/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql b/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql new file mode 100644 index 000000000000..20678da8dc0c --- /dev/null +++ b/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql @@ -0,0 +1,44 @@ +/** + * @name Duplicate key in dict literal + * @description Duplicate key in dict literal. All but the last will be lost. + * @kind problem + * @tags reliability + * useless-code + * external/cwe/cwe-561 + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/duplicate-key-dict-literal + */ + +import python +import semmle.python.strings + +predicate dict_key(Dict d, Expr k, string s) { + k = d.getAKey() and + ( + s = ((Num)k).getN() + or + // We use � to mark unrepresentable characters + // so two instances of � may represent different strings in the source code + not "�" = s.charAt(_) and + exists(StrConst c | + c = k | + s = "u\"" + c.getText() + "\"" and c.isUnicode() + or + s = "b\"" + c.getText() + "\"" and not c.isUnicode() + ) + ) +} + +from Dict d, Expr k1, Expr k2 +where exists(string s | dict_key(d, k1, s) and dict_key(d, k2, s) and k1 != k2) and +( + exists(BasicBlock b, int i1, int i2 | + k1.getAFlowNode() = b.getNode(i1) and + k2.getAFlowNode() = b.getNode(i2) and + i1 < i2 + ) or + k1.getAFlowNode().getBasicBlock().strictlyDominates(k2.getAFlowNode().getBasicBlock()) +) +select k1, "Dictionary key " + repr(k1) + " is subsequently $@.", k2, "overwritten" diff --git a/python/ql/src/Expressions/EqualsNone.py b/python/ql/src/Expressions/EqualsNone.py new file mode 100644 index 000000000000..d48507ff3ba3 --- /dev/null +++ b/python/ql/src/Expressions/EqualsNone.py @@ -0,0 +1,12 @@ + +def filter1(function, iterable=None) + if iterable == None: # Comparison using '__eq__' + return [item for item in iterable if item] + else: + return [item for item in iterable if function(item)] + +def filter2(function, iterable=None) + if iterable is None: # Comparison using identity + return [item for item in iterable if item] + else: + return [item for item in iterable if function(item)] diff --git a/python/ql/src/Expressions/EqualsNone.qhelp b/python/ql/src/Expressions/EqualsNone.qhelp new file mode 100644 index 000000000000..2ca33bc4b1bc --- /dev/null +++ b/python/ql/src/Expressions/EqualsNone.qhelp @@ -0,0 +1,34 @@ + + + +

    When you compare an object to None, use is rather than ==. +None is a singleton object, comparing using == invokes the __eq__ +method on the object in question, which may be slower than identity comparison. Comparing to +None using the is operator is also easier for other programmers to read.

    + + +
    + + +

    Replace == with is.

    + +
    + +

    The filter2 function is likely to be more efficient than the filter1 +function because it uses an identity comparison.

    + + + + +
    + + + +
  • Python Language Reference: Comparisons, +object.__eq__.
  • + + +
    +
    diff --git a/python/ql/src/Expressions/EqualsNone.ql b/python/ql/src/Expressions/EqualsNone.ql new file mode 100644 index 000000000000..fa36dffb7248 --- /dev/null +++ b/python/ql/src/Expressions/EqualsNone.ql @@ -0,0 +1,17 @@ +/** + * @name Testing equality to None + * @description Testing whether an object is 'None' using the == operator is inefficient and potentially incorrect. + * @kind problem + * @tags efficiency + * maintainability + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/test-equals-none + */ + +import python + +from Compare c +where c.getOp(0) instanceof Eq and c.getAComparator() instanceof None +select c, "Testing for None should use the 'is' operator." diff --git a/python/ql/src/Expressions/ExpectedMappingForFormatString.py b/python/ql/src/Expressions/ExpectedMappingForFormatString.py new file mode 100644 index 000000000000..410c044adf02 --- /dev/null +++ b/python/ql/src/Expressions/ExpectedMappingForFormatString.py @@ -0,0 +1,7 @@ + +def unsafe_format(): + if unlikely_condition(): + args = (1,2,3) + else: + args = {a:1,b:2,c:3} + return "%(a)s %(b)s %(c)s" % args \ No newline at end of file diff --git a/python/ql/src/Expressions/ExpectedMappingForFormatString.qhelp b/python/ql/src/Expressions/ExpectedMappingForFormatString.qhelp new file mode 100644 index 000000000000..8a9789b418e0 --- /dev/null +++ b/python/ql/src/Expressions/ExpectedMappingForFormatString.qhelp @@ -0,0 +1,30 @@ + + + +

    If a format string includes conversion specifiers of the form %(name)s then the right hand side of the operation must be a mapping. +A string is a format string if it appears on the left of a modulo (%) operator, the right hand side being the value to be formatted. +If the right hand side is not a mapping then a TypeError will be raised. +Mappings are usually dicts but can be any type that implements the mapping protocol. +

    + + +
    + +

    Change the format to match the arguments and ensure that the right hand side is always a mapping. + +

    + +

    In the following example the right hand side of the formatting operation can be a tuple, which is not a mapping. +To fix this example, ensure that args is a mapping when unlike_condition occurs. +

    + + +
    + + +
  • Python Library Reference: String Formatting.
  • + +
    +
    diff --git a/python/ql/src/Expressions/ExpectedMappingForFormatString.ql b/python/ql/src/Expressions/ExpectedMappingForFormatString.ql new file mode 100644 index 000000000000..9c398a3a6eee --- /dev/null +++ b/python/ql/src/Expressions/ExpectedMappingForFormatString.ql @@ -0,0 +1,19 @@ +/** + * @name Formatted object is not a mapping + * @description The formatted object must be a mapping when the format includes a named specifier; otherwise a TypeError will be raised." + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/percent-format/not-mapping + */ + +import python +import semmle.python.strings + +from Expr e, ClassObject t +where exists(BinaryExpr b | b.getOp() instanceof Mod and format_string(b.getLeft()) and e = b.getRight() and +mapping_format(b.getLeft()) and e.refersTo(_, t, _) and not t.isMapping()) +select e, "Right hand side of a % operator must be a mapping, not class $@.", t, t.getName() diff --git a/python/ql/src/Expressions/ExplicitCallToDel.py b/python/ql/src/Expressions/ExplicitCallToDel.py new file mode 100644 index 000000000000..59601a6f34df --- /dev/null +++ b/python/ql/src/Expressions/ExplicitCallToDel.py @@ -0,0 +1,16 @@ + + +def extract_bad(zippath, dest): + zipped = ZipFile(zippath) + try: + zipped.extractall(dest) + finally: + zipped.__del__() + +def extract_good(zippath, dest): + zipped = ZipFile(zippath) + try: + zipped.extractall(dest) + finally: + zipped.close() + diff --git a/python/ql/src/Expressions/ExplicitCallToDel.qhelp b/python/ql/src/Expressions/ExplicitCallToDel.qhelp new file mode 100644 index 000000000000..9ec18b46918e --- /dev/null +++ b/python/ql/src/Expressions/ExplicitCallToDel.qhelp @@ -0,0 +1,33 @@ + + + + +

    The __del__ special method is designed to be called by the Python virtual machine when an object is no longer reachable, +but before it is destroyed. Calling a __del__ method explicitly may cause an object to enter an unsafe state.

    + + +
    + + +

    If explicit clean up of an object is required, a close() method should be called or, better still, +wrap the use of the object in a with statement. +

    + +
    + +

    In the first example, rather than close the zip file in a conventional manner the programmer has called __del__. +A safer alternative is shown in the second example. +

    + + + + +
    + + +
  • Python Standard Library: object.__del__
  • + +
    +
    diff --git a/python/ql/src/Expressions/ExplicitCallToDel.ql b/python/ql/src/Expressions/ExplicitCallToDel.ql new file mode 100644 index 000000000000..1cb2782c8859 --- /dev/null +++ b/python/ql/src/Expressions/ExplicitCallToDel.ql @@ -0,0 +1,35 @@ +/** + * @name __del__ is called explicitly + * @description The __del__ special method is called by the virtual machine when an object is being finalized. It should not be called explicitly. + * @kind problem + * @tags reliability + * correctness + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/explicit-call-to-delete + */ + +import python + +class DelCall extends Call { + DelCall() { + ((Attribute)this.getFunc()).getName() = "__del__" + } + + predicate isSuperCall() { + exists(Function f | f = this.getScope() and f.getName() = "__del__" | + // We pass in `self` as the first argument... + f.getArg(0).asName().getVariable() = ((Name)this.getArg(0)).getVariable() or + // ... or the call is of the form `super(Type, self).__del__()`, or the equivalent + // Python 3: `super().__del__()`. + exists(Call superCall | superCall = ((Attribute)this.getFunc()).getObject() | + ((Name)superCall.getFunc()).getId() = "super" + ) + ) + } +} + +from DelCall del +where not del.isSuperCall() +select del, "The __del__ special method is called explicitly." \ No newline at end of file diff --git a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll new file mode 100644 index 000000000000..7a451ada1bd1 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll @@ -0,0 +1,142 @@ +import python + + +library class PossibleAdvancedFormatString extends StrConst { + + PossibleAdvancedFormatString() { + this.getText().matches("%{%}%") + } + + private predicate field(int start, int end) { + brace_pair(this, start, end) and + this.getText().substring(start, end) != "{{}}" + } + + /** Gets the number of the formatting field at [start, end) */ + int getFieldNumber(int start, int end) { + result = this.fieldId(start, end).toInt() + or + this.implicitlyNumberedField(start, end) and + result = count(int s | this.implicitlyNumberedField(s, _) and s < start) + } + + /** Gets the text of the formatting field at [start, end) */ + string getField(int start, int end) { + this.field(start, end) and + result = this.getText().substring(start, end) + } + + private string fieldId(int start, int end) { + this.field(start, end) and + ( + result = this.getText().substring(start, end).regexpCapture("\\{([^!:.\\[]+)[!:.\\[].*", 1) + or + result = this.getText().substring(start+1, end-1) and result.regexpMatch("[^!:.\\[]+") + ) + } + + /** Gets the name of the formatting field at [start, end) */ + string getFieldName(int start, int end) { + result = this.fieldId(start, end) + and not exists(this.getFieldNumber(start, end)) + } + + private predicate implicitlyNumberedField(int start, int end) { + this.field(start, end) and + exists(string c | + start+1 = this.getText().indexOf(c) | + c = "}" or c = ":" or c = "!" or c = "." + ) + } + + /** Whether this format string has implicitly numbered fields */ + predicate isImplicitlyNumbered() { + this.implicitlyNumberedField(_, _) + } + + /** Whether this format string has explicitly numbered fields */ + predicate isExplicitlyNumbered() { + exists(this.fieldId(_, _).toInt()) + } + +} + +predicate brace_sequence(PossibleAdvancedFormatString fmt, int index, int len) { + exists(string text | + text = fmt.getText() | + text.charAt(index) = "{" and not text.charAt(index-1) = "{" and len = 1 + or + text.charAt(index) = "{" and text.charAt(index-1) = "{" and brace_sequence(fmt, index-1, len-1) + ) +} + +predicate escaped_brace(PossibleAdvancedFormatString fmt, int index) { + exists(int len | + brace_sequence(fmt, index, len) | + len % 2 = 0 + ) +} + +predicate escaping_brace(PossibleAdvancedFormatString fmt, int index) { + escaped_brace(fmt, index+1) +} + +private predicate inner_brace_pair(PossibleAdvancedFormatString fmt, int start, int end) { + not escaping_brace(fmt, start) and + not escaped_brace(fmt, start) and + fmt.getText().charAt(start) = "{" and + exists(string pair | pair = fmt.getText().suffix(start).regexpCapture("(?s)(\\{([^{}]|\\{\\{)*+\\}).*", 1) | + end = start + pair.length() + ) +} + +private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int end) { + inner_brace_pair(fmt, start, end) + or + not escaping_brace(fmt, start) and + not escaped_brace(fmt, start) and + exists(string prefix, string postfix, int innerstart, int innerend | + brace_pair(fmt, innerstart, innerend) and + prefix = fmt.getText().regexpFind("\\{([^{}]|\\{\\{)+\\{", _, start) and + innerstart = start+prefix.length()-1 and + postfix = fmt.getText().regexpFind("\\}([^{}]|\\}\\})*\\}", _, innerend-1) and + end = innerend + postfix.length()-1 + ) +} + +private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) { + exists(CallNode call | + call = format_expr.getAFlowNode() | + call.getFunction().refersTo(theFormatFunction()) and call.getArg(0).refersTo(_, fmt.getAFlowNode()) and + args = count(format_expr.getAnArg()) - 1 + or + call.getFunction().(AttrNode).getObject("format").refersTo(_, fmt.getAFlowNode()) and + args = count(format_expr.getAnArg()) + ) +} + +class AdvancedFormatString extends PossibleAdvancedFormatString { + + AdvancedFormatString() { + advanced_format_call(_, this, _) + } + +} + +class AdvancedFormattingCall extends Call { + + AdvancedFormattingCall() { + advanced_format_call(this, _, _) + } + + /** Count of the arguments actually provided */ + int providedArgCount() { + advanced_format_call(this, _, result) + } + + AdvancedFormatString getAFormat() { + advanced_format_call(this, result, _) + } + +} + diff --git a/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.py b/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.py new file mode 100644 index 000000000000..1d483cdf24c4 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.py @@ -0,0 +1,2 @@ +def illegal_format(): + "{} {1}".format("spam", "eggs") diff --git a/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.qhelp b/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.qhelp new file mode 100644 index 000000000000..014ba097cdd5 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.qhelp @@ -0,0 +1,31 @@ + + + +

    A formatting expression, that is an expression of the form the_format.format(args) or format(the_format, args), +can use explicitly numbered fields, like {1}, or implicitly numbered fields, such as {}, but it cannot use both. +Doing so will raise a ValueError. +

    + +
    + +

    +Use either explicitly numbered fields or implicitly numbered fields, but be consistent. +

    + +
    + +

    +In the following example the formatting uses both implicit, {}, and explicit, {1}, numbering for fields, which is illegal. +

    + + +
    + + +
  • Python Library Reference: String Formatting.
  • + + +
    +
    diff --git a/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql b/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql new file mode 100644 index 000000000000..3f488aa95077 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql @@ -0,0 +1,18 @@ +/** + * @name Formatting string mixes implicitly and explicitly numbered fields + * @description Using implicit and explicit numbering in string formatting operations, such as '"{}: {1}".format(a,b)', will raise a ValueError. + * @kind problem + * @problem.severity error + * @tags reliability + * correctness + * @sub-severity low + * @precision high + * @id py/str-format/mixed-fields + */ + +import python +import AdvancedFormatting + +from AdvancedFormattingCall call, AdvancedFormatString fmt +where call.getAFormat() = fmt and fmt.isImplicitlyNumbered() and fmt.isExplicitlyNumbered() +select fmt, "Formatting string mixes implicitly and explicitly numbered fields." \ No newline at end of file diff --git a/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.py b/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.py new file mode 100644 index 000000000000..591f461437ad --- /dev/null +++ b/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.py @@ -0,0 +1,3 @@ +def surplus_argument(): + the_format = "{} {}" # Used to be "{} {} {}" + return the_format.format(1, 2, 3) diff --git a/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.qhelp b/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.qhelp new file mode 100644 index 000000000000..707ddaf7181d --- /dev/null +++ b/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.qhelp @@ -0,0 +1,33 @@ + + + +

    A formatting expression, that is an expression of the form the_format.format(args) or format(the_format, args), +can have any number of arguments, provided that there are enough to match the format. +However, surplus arguments are redundant and clutter the code, making it harder to read. +

    + +

    +It is also possible that surplus arguments indicate a mistake in the format string. +

    + +
    + +

    +Check that the format string is correct and then remove any surplus arguments. +

    + +
    + +

    In the following example there are three arguments for the call to the str.format() method, but the format string only requires two. +The third argument should be deleted.

    + + +
    + + +
  • Python Library Reference: String Formatting.
  • + +
    +
    diff --git a/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql b/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql new file mode 100644 index 000000000000..67c952773752 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql @@ -0,0 +1,26 @@ +/** + * @name Unused argument in a formatting call + * @description Including surplus arguments in a formatting call makes code more difficult to read and may indicate an error. + * @kind problem + * @tags maintainability + * useless-code + * @problem.severity warning + * @sub-severity high + * @precision high + * @id py/str-format/surplus-argument + */ + +import python + + +import python +import AdvancedFormatting + +int field_count(AdvancedFormatString fmt) { result = max(fmt.getFieldNumber(_, _)) + 1 } + +from AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field +where arg_count = call.providedArgCount() and max_field = field_count(fmt) and +call.getAFormat() = fmt and not exists(call.getStarargs()) and +forall(AdvancedFormatString other | other = call.getAFormat() | field_count(other) < arg_count) +select call, "Too many arguments for string format. Format $@ requires only " + max_field + ", but " + +arg_count.toString() + " are provided.", fmt, "\"" + fmt.getText() + "\"" diff --git a/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.py b/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.py new file mode 100644 index 000000000000..e2c3b0cde929 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.py @@ -0,0 +1,3 @@ +def surplus_argument(): + the_format = "{spam} {eggs}" # Used to be "{spam} {eggs} {chips}" + return the_format.format(spam = "spam", eggs="eggs", chips="chips") diff --git a/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.qhelp b/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.qhelp new file mode 100644 index 000000000000..12e482ae2c89 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.qhelp @@ -0,0 +1,35 @@ + + + +

    A formatting expression, that is an expression of the form the_format.format(args) or format(the_format, args) +can have keyword arguments of any name, as long as all the required names are provided. +However, surplus keyword arguments, those with names that are not in the format, are redundant. +These surplus arguments clutter the code, making it harder to read. +

    + +

    +It is also possible that surplus keyword arguments indicate a mistake in the format string. +

    + +
    + +

    +Check that the format string is correct and then remove any surplus keyword arguments. +

    + +
    + +

    In the following example, the comment indicates that the chips keyword argument is no longer required and should be deleted. +

    + + +
    + + +
  • Python Library Reference: String Formatting.
  • + + +
    +
    diff --git a/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql b/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql new file mode 100644 index 000000000000..c902b992b1cc --- /dev/null +++ b/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql @@ -0,0 +1,27 @@ +/** + * @name Unused named argument in formatting call + * @description Including surplus keyword arguments in a formatting call makes code more difficult to read and may indicate an error. + * @kind problem + * @tags maintainability + * useless-code + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/str-format/surplus-named-argument + */ + +import python +import AdvancedFormatting + +from AdvancedFormattingCall call, AdvancedFormatString fmt, string name, string fmt_repr +where call.getAFormat() = fmt and +name = call.getAKeyword().getArg() and +forall(AdvancedFormatString format | format = call.getAFormat() | not format.getFieldName(_, _) = name) +and not exists(call.getKwargs()) and +(strictcount(call.getAFormat()) = 1 and fmt_repr = "format \"" + fmt.getText() + "\"" + or + strictcount(call.getAFormat()) != 1 and fmt_repr = "any format used." +) + +select call, "Surplus named argument for string format. An argument named '" + name + + "' is provided, but it is not required by $@.", fmt, fmt_repr diff --git a/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.py b/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.py new file mode 100644 index 000000000000..21fe107eaf91 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.py @@ -0,0 +1,6 @@ +def unsafe_named_format(): + the_format = "{spam} {eggs}" + if unlikely_condition(): + return the_format.format(spam="spam", completely_different="eggs") + else: + return the_format.format(spam="spam", eggs="eggs") diff --git a/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.qhelp b/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.qhelp new file mode 100644 index 000000000000..39c75a0c67f1 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.qhelp @@ -0,0 +1,31 @@ + + + +

    A formatting expression, that is an expression of the form the_format.format(args) or format(the_format, args), +can use named fields. If it does, then keyword arguments must be supplied for all named fields. +If any of the keyword arguments are missing then a KeyError will be raised. +

    + +
    + +

    +Change the format to match the arguments and ensure that the arguments have the correct names. +

    + +
    + +

    In the following example, if unlikely_condition() is true, then a KeyError will be raised +as the keyword parameter eggs is missing. +Adding a keyword parameter named eggs would fix this. +

    + + +
    + + +
  • Python Library Reference: String Formatting.
  • + +
    +
    diff --git a/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql b/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql new file mode 100644 index 000000000000..412d8d558300 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql @@ -0,0 +1,23 @@ +/** + * @name Missing named arguments in formatting call + * @description A string formatting operation, such as '"{name}".format(key=b)', + * where the names of format items in the format string differs from the names of the values to be formatted will raise a KeyError. + * @kind problem + * @problem.severity error + * @tags reliability + * correctness + * @sub-severity low + * @precision high + * @id py/str-format/missing-named-argument + */ + +import python +import AdvancedFormatting + +from AdvancedFormattingCall call, AdvancedFormatString fmt, string name +where call.getAFormat() = fmt and +not name = call.getAKeyword().getArg() and +fmt.getFieldName(_, _) = name +and not exists(call.getKwargs()) +select call, "Missing named argument for string format. Format $@ requires '" + name + "', but it is omitted.", +fmt, "\"" + fmt.getText() + "\"" \ No newline at end of file diff --git a/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.py b/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.py new file mode 100644 index 000000000000..0105d035e325 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.py @@ -0,0 +1,7 @@ +def unsafe_format(): + the_format = "{} {} {}" + if unlikely_condition(): + return the_format.format(1, 2) + else: + return the_format.format(1, 2, 3) + diff --git a/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.qhelp b/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.qhelp new file mode 100644 index 000000000000..bc342bd2c05c --- /dev/null +++ b/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.qhelp @@ -0,0 +1,29 @@ + + + +

    A formatting expression, that is an expression of the form the_format.format(args) or format(the_format, args), +must have sufficient arguments to match the format. Otherwise, an IndexError will be raised. +

    + +
    + +

    +Either change the format to match the arguments, or ensure that there are sufficient arguments. +

    + +
    + +

    In the following example, only 2 arguments may be provided for the call to the str.format method, +which is insufficient for the format string used. To fix this a third parameter should be provided on line 4. +

    + + +
    + + +
  • Python Library Reference: String Formatting.
  • + +
    +
    diff --git a/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql b/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql new file mode 100644 index 000000000000..fe766ae2d8b4 --- /dev/null +++ b/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql @@ -0,0 +1,23 @@ +/** + * @name Too few arguments in formatting call + * @description A string formatting operation, such as '"{0}: {1}, {2}".format(a,b)', + * where the number of values to be formatted is too few for the format string will raise an IndexError. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/str-format/missing-argument + */ + +import python +import AdvancedFormatting + +from AdvancedFormattingCall call, AdvancedFormatString fmt, +int arg_count, int max_field, string provided +where arg_count = call.providedArgCount() and max_field = max(fmt.getFieldNumber(_, _)) and +call.getAFormat() = fmt and not exists(call.getStarargs()) and arg_count <= max_field and +(if arg_count = 1 then provided = " is provided." else provided = " are provided.") +select call, "Too few arguments for string format. Format $@ requires at least " + (max_field+1) + ", but " + +arg_count.toString() + provided, fmt, "\"" + fmt.getText() + "\"" \ No newline at end of file diff --git a/python/ql/src/Expressions/HashedButNoHash.py b/python/ql/src/Expressions/HashedButNoHash.py new file mode 100644 index 000000000000..dad0d5ea8627 --- /dev/null +++ b/python/ql/src/Expressions/HashedButNoHash.py @@ -0,0 +1,5 @@ + +def lookup_with_default_key(mapping, key=None): + if key is None: + key = [] # Should be key = () + return mapping[key] diff --git a/python/ql/src/Expressions/HashedButNoHash.qhelp b/python/ql/src/Expressions/HashedButNoHash.qhelp new file mode 100644 index 000000000000..6df27d4e60f2 --- /dev/null +++ b/python/ql/src/Expressions/HashedButNoHash.qhelp @@ -0,0 +1,43 @@ + + + +

    If an object is used as a key in a dictionary or as a member of a set then it must be hashable, +that is it must define a __hash__ method. All built-in immutable types are hashable, but +mutable ones are not. Common hashable types include all numbers, strings (both unicode and bytes) +and tuple. Common unhashable types include list, dict and set. +

    + +

    +In order to store a key in a dict or set a hash value is needed. To determine this value the built-in +function hash() is called which in turn calls the __hash__ method on the object. +If the object's class does not have the __hash__ method, then a TypeError will be raised. +

    + + +
    + +

    Since this problem usually indicates a logical error, it is not possible to give a general recipe for fixing it. +Mutable collections can be converted into immutable equivalents where appropriate. For example sets can be hashed by converting any instances +of set into frozenset instances. +

    + +
    + +

    lists are not hashable. In this example, an attempt is made to use a list +as a key in a mapping which will fail with a TypeError. +

    + + + +
    + + +
  • Python Standard Library: hash.
  • +
  • Python Language Reference: object.__hash__.
  • +
  • Python Standard Library: Mapping Types — dict.
  • +
  • Python Standard Library: Set Types — set, frozenset.
  • + +
    +
    diff --git a/python/ql/src/Expressions/HashedButNoHash.ql b/python/ql/src/Expressions/HashedButNoHash.ql new file mode 100644 index 000000000000..310d56b273a5 --- /dev/null +++ b/python/ql/src/Expressions/HashedButNoHash.ql @@ -0,0 +1,59 @@ +/** + * @name Unhashable object hashed + * @description Hashing an object which is not hashable will result in a TypeError at runtime. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/hash-unhashable-value + */ + +import python + +/* This assumes that any indexing operation where the value is not a sequence or numpy array involves hashing. + * For sequences, the index must be an int, which are hashable, so we don't need to treat them specially. + * For numpy arrays, the index may be a list, which are not hashable and needs to be treated specially. + */ + +predicate numpy_array_type(ClassObject na) { + exists(ModuleObject np | np.getName() = "numpy" or np.getName() = "numpy.core" | + na.getAnImproperSuperType() = np.getAttribute("ndarray") + ) +} + +predicate has_custom_getitem(ClassObject cls) { + cls.lookupAttribute("__getitem__") instanceof PyFunctionObject + or + numpy_array_type(cls) +} + +predicate explicitly_hashed(ControlFlowNode f) { + exists(CallNode c, GlobalVariable hash | c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash") +} + +predicate unhashable_subscript(ControlFlowNode f, ClassObject c, ControlFlowNode origin) { + is_unhashable(f, c, origin) and + exists(SubscriptNode sub | sub.getIndex() = f | + exists(ClassObject custom_getitem | + sub.getObject().refersTo(_, custom_getitem, _) and + not has_custom_getitem(custom_getitem) + ) + ) +} + +predicate is_unhashable(ControlFlowNode f, ClassObject cls, ControlFlowNode origin) { + f.refersTo(_, cls, origin) and + (not cls.hasAttribute("__hash__") and not cls.unknowableAttributes() and cls.isNewStyle() + or + cls.lookupAttribute("__hash__") = theNoneObject() + ) +} + +from ControlFlowNode f, ClassObject c, ControlFlowNode origin +where +explicitly_hashed(f) and is_unhashable(f, c, origin) +or +unhashable_subscript(f, c, origin) +select f.getNode(), "This $@ of $@ is unhashable.", origin, "instance", c, c.getQualifiedName() diff --git a/python/ql/src/Expressions/IncorrectComparisonUsingIs.py b/python/ql/src/Expressions/IncorrectComparisonUsingIs.py new file mode 100644 index 000000000000..faf78fd8f120 --- /dev/null +++ b/python/ql/src/Expressions/IncorrectComparisonUsingIs.py @@ -0,0 +1,27 @@ + +DEFAULT = "default" + +def get_color(name, fallback): + if name in COLORS: + return COLORS[name] + elif fallback is DEFAULT: + return DEFAULT_COLOR + else: + return fallback + +#This works +print (get_color("spam", "def" + "ault")) + +#But this does not +print (get_color("spam", "default-spam"[:7])) + +#To fix the above code change to object +DEFAULT = object() + +#Or if you want better repr() output: +class Default(object): + + def __repr__(self): + return "DEFAULT" + +DEFAULT = Default() diff --git a/python/ql/src/Expressions/IncorrectComparisonUsingIs.qhelp b/python/ql/src/Expressions/IncorrectComparisonUsingIs.qhelp new file mode 100644 index 000000000000..b8c25fa04a24 --- /dev/null +++ b/python/ql/src/Expressions/IncorrectComparisonUsingIs.qhelp @@ -0,0 +1,43 @@ + + + + +

    When you compare two values using the is or is not operator, it is the +object identities of the two values that is tested rather than their equality. + If the class of either of the values in the comparison redefines equality then the + is operator may return False even though the objects compare as equal. + Equality is defined by the __eq__ or, in Python2, __cmp__ method. + To compare two objects for equality, use the == or != operator instead.

    + +
    + + +

    When you want to compare the value of two literals, use the comparison operator == or +!= in place of is or is not.

    + +

    If the uniqueness property or performance are important then use an object that does not redefine equality.

    + +
    + + +

    In the first line of the following example the programmer tests the value of value against +DEFAULT using the is operator. Unfortunately, this may fail when the function +is called with the string "default".

    +

    +To function correctly, change the expression value is DEFAULT to value == DEFAULT. +Alternatively, if the uniqueness property is desirable, then change the definition of DEFAULT to +either of the alternatives below. +

    + + + + +
    + + +
  • Python Standard Library: Comparisons.
  • + +
    +
    diff --git a/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql b/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql new file mode 100644 index 000000000000..1cb59866e5ac --- /dev/null +++ b/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql @@ -0,0 +1,20 @@ +/** + * @name Comparison using is when operands support __eq__ + * @description Comparison using 'is' when equivalence is not the same as identity + * @kind problem + * @tags reliability + * correctness + * @problem.severity warning + * @sub-severity low + * @precision high + * @id py/comparison-using-is + */ + +import python +import IsComparisons + +from Compare comp, Cmpop op, ClassObject c, string alt +where invalid_portable_is_comparison(comp, op, c) and +not cpython_interned_constant(comp.getASubExpression()) and +(op instanceof Is and alt = "==" or op instanceof IsNot and alt = "!=") +select comp, "Values compared using '" + op.getSymbol() + "' when equivalence is not the same as identity. Use '" + alt + "' instead." diff --git a/python/ql/src/Expressions/IsComparisons.qll b/python/ql/src/Expressions/IsComparisons.qll new file mode 100644 index 000000000000..270c951f3cba --- /dev/null +++ b/python/ql/src/Expressions/IsComparisons.qll @@ -0,0 +1,112 @@ +import python + + +predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) { + exists(CompareNode fcomp | fcomp = comp.getAFlowNode() | + fcomp.operands(left, op, right) and (op instanceof Is or op instanceof IsNot) + ) +} + +predicate overrides_eq_or_cmp(ClassObject c) { + major_version() = 2 and c.hasAttribute("__eq__") + or + c.declaresAttribute("__eq__") and not c = theObjectType() + or + exists(ClassObject sup | + sup = c.getASuperType() and not sup = theObjectType() | + sup.declaresAttribute("__eq__") + ) + or + major_version() = 2 and c.hasAttribute("__cmp__") +} + +predicate invalid_to_use_is_portably(ClassObject c) { + overrides_eq_or_cmp(c) and + /* Exclude type/builtin-function/bool as it is legitimate to compare them using 'is' but they implement __eq__ */ + not c = theTypeType() and not c = theBuiltinFunctionType() and not c = theBoolType() and + /* OK to compare with 'is' if a singleton */ + not exists(c.getProbableSingletonInstance()) +} + +predicate simple_constant(ControlFlowNode f) { + exists(Object obj | f.refersTo(obj) | obj = theTrueObject() or obj = theFalseObject() or obj = theNoneObject()) +} + +private predicate cpython_interned_value(Expr e) { + exists(string text | text = e.(StrConst).getText() | + text.length() = 0 or + text.length() = 1 and text.regexpMatch("[U+0000-U+00ff]") + ) + or + exists(int i | + i = e.(IntegerLiteral).getN().toInt() | + -5 <= i and i <= 256 + ) + or + exists(Tuple t | t = e and not exists(t.getAnElt())) +} + +/** The set of values that can be expected to be interned across + * the main implementations of Python. PyPy, Jython, etc tend to + * follow CPython, but it varies, so this is a best guess. + */ +private predicate universally_interned_value(Expr e) { + e.(IntegerLiteral).getN().toInt() = 0 + or + exists(Tuple t | t = e and not exists(t.getAnElt())) + or + e.(StrConst).getText() = "" +} + +predicate cpython_interned_constant(Expr e) { + exists(Expr const | + e.refersTo(_, const) | + cpython_interned_value(const) + ) +} + +predicate universally_interned_constant(Expr e) { + exists(Expr const | + e.refersTo(_, const) | + universally_interned_value(const) + ) +} + +private predicate comparison_both_types(Compare comp, Cmpop op, ClassObject cls1, ClassObject cls2) { + exists(ControlFlowNode op1, ControlFlowNode op2 | + comparison_using_is(comp, op1, op, op2) or comparison_using_is(comp, op2, op, op1) | + op1.refersTo(_, cls1, _) and + op2.refersTo(_, cls2, _) + ) +} + +private predicate comparison_one_type(Compare comp, Cmpop op, ClassObject cls) { + not comparison_both_types(comp, _, _, _) and + exists(ControlFlowNode operand | + comparison_using_is(comp, operand, op, _) or comparison_using_is(comp, _, op, operand) | + operand.refersTo(_, cls, _) + ) +} + +predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassObject cls) { + /* OK to use 'is' when defining '__eq__' */ + not exists(Function eq | eq.getName() = "__eq__" or eq.getName() = "__ne__" | eq = comp.getScope().getScope*()) + and + ( + comparison_one_type(comp, op, cls) and invalid_to_use_is_portably(cls) + or + exists(ClassObject other | comparison_both_types(comp, op, cls, other) | + invalid_to_use_is_portably(cls) and + invalid_to_use_is_portably(other) + ) + ) + and + /* OK to use 'is' when comparing items from a known set of objects */ + not exists(Expr left, Expr right, Object obj | + comp.compares(left, op, right) and + left.refersTo(obj) and right.refersTo(obj) and + exists(ImmutableLiteral il | il.getLiteralObject() = obj) + ) +} + + diff --git a/python/ql/src/Expressions/NonCallableCalled.py b/python/ql/src/Expressions/NonCallableCalled.py new file mode 100644 index 000000000000..76b6a9208614 --- /dev/null +++ b/python/ql/src/Expressions/NonCallableCalled.py @@ -0,0 +1,2 @@ +a_list = [] +a_list() diff --git a/python/ql/src/Expressions/NonCallableCalled.qhelp b/python/ql/src/Expressions/NonCallableCalled.qhelp new file mode 100644 index 000000000000..35411ca62b32 --- /dev/null +++ b/python/ql/src/Expressions/NonCallableCalled.qhelp @@ -0,0 +1,39 @@ + + + +

    If an object is called, obj(), then that object must be a callable or +a TypeError will be raised. A callable object is any object whose class defines +the __call__ special method. +Callable objects include functions, methods, classes.

    + +

    The callable(object) builtin function determines if an object is callable or not.

    + +

    +When the Python interpreter attempts to evaluate a call such as func(arg) it will +invoke the __call__ special method on func. +Thus, func(arg) is roughly equivalent to type(func).__call__(func, arg) +which means that the class must define the attribute __call__, +merely adding it to the instance is not sufficient. +

    + +
    + +

    Since this problem usually indicates a logical error, it is not possible to give a general recipe for fixing it.

    + +
    + +

    lists are not callable. In this example, an attempt is made to call a list +which will fail with a TypeError. +

    + + +
    + + +
  • Python Standard Library: callable.
  • +
  • Python Language Reference: object.__call__.
  • + +
    +
    diff --git a/python/ql/src/Expressions/NonCallableCalled.ql b/python/ql/src/Expressions/NonCallableCalled.ql new file mode 100644 index 000000000000..5d58ae04ec93 --- /dev/null +++ b/python/ql/src/Expressions/NonCallableCalled.ql @@ -0,0 +1,24 @@ +/** + * @name Non-callable called + * @description A call to an object which is not a callable will raise a TypeError at runtime. + * @kind problem + * @tags reliability + * correctness + * types + * @problem.severity error + * @sub-severity high + * @precision high + * @id py/call-to-non-callable + */ + +import python +import Exceptions.NotImplemented + +from Call c, ClassObject t, Expr f, AstNode origin +where f = c.getFunc() and f.refersTo(_, t, origin) and + not t.isCallable() and not t.unknowableAttributes() + and not t.isDescriptorType() + and not t = theNoneType() + and not use_of_not_implemented_in_raise(_, f) + +select c, "Call to a $@ of $@.", origin, "non-callable", t, t.toString() diff --git a/python/ql/src/Expressions/NonPortableComparisonUsingIs.py b/python/ql/src/Expressions/NonPortableComparisonUsingIs.py new file mode 100644 index 000000000000..12be9d143e78 --- /dev/null +++ b/python/ql/src/Expressions/NonPortableComparisonUsingIs.py @@ -0,0 +1,8 @@ + +CONSTANT = 12 + +def equals_to_twelve(x): + return x is CONSTANT + +#This works in CPython, but might not for other implementations. +print (equals_to_twelve(5 + 7)) diff --git a/python/ql/src/Expressions/NonPortableComparisonUsingIs.qhelp b/python/ql/src/Expressions/NonPortableComparisonUsingIs.qhelp new file mode 100644 index 000000000000..4b5fa7c840a1 --- /dev/null +++ b/python/ql/src/Expressions/NonPortableComparisonUsingIs.qhelp @@ -0,0 +1,44 @@ + + + + +

    When you compare two values using the is or is not operator, it is the +object identities of the two values that is tested rather than their equality. +If the class of either of the values in the comparison redefines equality then the +is operator may return False even though the objects compare as equal. +

    +

    +CPython interns a number of commonly used values, such as small integers, which means that using +is instead of == will work correctly. However, this might not be portable +to other implementations such as PyPy, IronPython, Jython or MicroPython. +

    + +
    + + +

    When you want to compare the value of two literals, use the comparison operator == or +!= in place of is or is not.

    + +

    If the uniqueness property or performance are important then use an object that does not redefine equality.

    + +
    + + +

    The function equals_to_twelve() relies on CPython interning small integers.

    +

    +To function correctly for all implementations, change the expression x is CONSTANT to x == CONSTANT. +

    + + + + +
    + + +
  • Python Standard Library: Comparisons.
  • +
  • Stack Overflow: Python "is" operator behaves unexpectedly with integers.
  • + +
    +
    diff --git a/python/ql/src/Expressions/NonPortableComparisonUsingIs.ql b/python/ql/src/Expressions/NonPortableComparisonUsingIs.ql new file mode 100644 index 000000000000..397aec01065c --- /dev/null +++ b/python/ql/src/Expressions/NonPortableComparisonUsingIs.ql @@ -0,0 +1,23 @@ +/** + * @name Non-portable comparison using is when operands support __eq__ + * @description Comparison using 'is' when equivalence is not the same as identity and may not be portable. + * @kind problem + * @tags portability + * maintainability + * @problem.severity recommendation + * @sub-severity low + * @precision medium + * @id py/comparison-using-is-non-portable + */ + +import python +import IsComparisons + +from Compare comp, Cmpop op, ClassObject c +where invalid_portable_is_comparison(comp, op, c) and +exists(Expr sub | + sub = comp.getASubExpression() | + cpython_interned_constant(sub) and + not universally_interned_constant(sub) +) +select comp, "The result of this comparison with '" + op.getSymbol() + "' may differ between implementations of Python." \ No newline at end of file diff --git a/python/ql/src/Expressions/RedundantComparison.qll b/python/ql/src/Expressions/RedundantComparison.qll new file mode 100644 index 000000000000..64f80ce31b5c --- /dev/null +++ b/python/ql/src/Expressions/RedundantComparison.qll @@ -0,0 +1,46 @@ +import python + +class RedundantComparison extends Compare { + + RedundantComparison() { + exists(Expr left, Expr right | + this.compares(left, _, right) + and + same_variable(left, right) + ) + } + + predicate maybeMissingSelf() { + exists(Name left | + this.compares(left, _, _) and + not this.isConstant() and + exists(Class cls | left.getScope().getScope() = cls | + exists(SelfAttribute sa | sa.getName() = left.getId() | + sa.getClass() = cls + ) + ) + ) + } + +} + +private predicate same_variable(Expr left, Expr right) { + same_name(left, right) + or + same_attribute(left, right) +} + +private predicate name_in_comparison(Compare comp, Name n, Variable v) { + comp.contains(n) and v = n.getVariable() +} + +private predicate same_name(Name n1, Name n2) { + n1 != n2 and + exists(Compare comp, Variable v | name_in_comparison(comp, n1, v) and name_in_comparison(comp, n2, v)) +} + +private predicate same_attribute(Attribute a1, Attribute a2) { + a1 != a2 and + exists(Compare comp | comp.contains(a1) and comp.contains(a2)) and + a1.getName() = a2.getName() and same_name(a1.getObject(), a2.getObject()) +} diff --git a/python/ql/src/Expressions/Regex/BackspaceEscape.py b/python/ql/src/Expressions/Regex/BackspaceEscape.py new file mode 100644 index 000000000000..2c1fa4bad4ef --- /dev/null +++ b/python/ql/src/Expressions/Regex/BackspaceEscape.py @@ -0,0 +1,5 @@ +import re +matcher = re.compile(r"\b[\t\b]") + +def match_data(data): + return bool(matcher.match(data)) diff --git a/python/ql/src/Expressions/Regex/BackspaceEscape.qhelp b/python/ql/src/Expressions/Regex/BackspaceEscape.qhelp new file mode 100644 index 000000000000..18599e13e641 --- /dev/null +++ b/python/ql/src/Expressions/Regex/BackspaceEscape.qhelp @@ -0,0 +1,40 @@ + + + + +

    +The meaning of the \b escape sequence inside a regular expression depends on its +syntactic context: inside a character class, it matches the backspace character; outside of a +character class, it matches a word boundary. This context dependency makes regular expressions +hard to read, so the \b escape sequence should not be used inside character classes. +

    + +
    + + +

    +Replace \b in character classes with the semantically identical escape sequence \x08. +

    + +
    + +

    +In the following example, the regular expression contains two uses of \b: in the +first case, it matches a word boundary, in the second case it matches a backspace character. +

    + + + +

    +You can make the regular expression easier for other developers to interpret, by rewriting it as r"\b[\t\x08]". +

    + +
    + + +
  • Python Standard Library: Regular expression operations.
  • + +
    +
    diff --git a/python/ql/src/Expressions/Regex/BackspaceEscape.ql b/python/ql/src/Expressions/Regex/BackspaceEscape.ql new file mode 100644 index 000000000000..b80893b04f00 --- /dev/null +++ b/python/ql/src/Expressions/Regex/BackspaceEscape.ql @@ -0,0 +1,22 @@ +/** + * @name Backspace escape in regular expression + * @description Using '\b' to escape the backspace character in a regular expression is confusing + * since it could be mistaken for a word boundary assertion. + * @kind problem + * @tags maintainability + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/regex/backspace-escape + */ + +import python +import semmle.python.regex + +from Regex r, int offset +where r.escapingChar(offset) and r.getChar(offset+1) = "b" and +exists(int start, int end | + start < offset and end > offset | + r.charSet(start, end) +) +select r, "Backspace escape in regular expression at offset " + offset + "." \ No newline at end of file diff --git a/python/ql/src/Expressions/Regex/DuplicateCharacterInSet.py b/python/ql/src/Expressions/Regex/DuplicateCharacterInSet.py new file mode 100644 index 000000000000..cf2b0511235d --- /dev/null +++ b/python/ql/src/Expressions/Regex/DuplicateCharacterInSet.py @@ -0,0 +1,6 @@ +import re +matcher = re.compile(r"[password|pwd]") + +def find_password(data): + if matcher.match(data): + print("Found password!") diff --git a/python/ql/src/Expressions/Regex/DuplicateCharacterInSet.qhelp b/python/ql/src/Expressions/Regex/DuplicateCharacterInSet.qhelp new file mode 100644 index 000000000000..d9b1e9ed6d91 --- /dev/null +++ b/python/ql/src/Expressions/Regex/DuplicateCharacterInSet.qhelp @@ -0,0 +1,44 @@ + + + + +

    +Character classes in regular expressions represent sets of characters, so there is no need to specify +the same character twice in one character class. Duplicate characters in character classes are at best +useless, and may even indicate a latent bug. +

    + +
    + + +

    Determine whether a character is simply duplicated or whether the character class was in fact meant as a group. +If it is just a duplicate, then remove the duplicate character. +If was supposed to be a group, then replace the square brackets with parentheses. +

    + + +
    + +

    +In the following example, the character class [password|pwd] contains two instances each +of the characters d, p, s, and w. The programmer most likely meant +to write (password|pwd) (a pattern that matches either the string "password" +or the string "pwd"), and accidentally mistyped the enclosing brackets. +

    + + + +

    +To fix this problem, the regular expression should be rewritten to r"(password|pwd)". +

    + +
    + + +
  • Python Standard Library: Regular expression operations.
  • +
  • Regular-Expressions.info: Character Classes or Character Sets.
  • + +
    +
    diff --git a/python/ql/src/Expressions/Regex/DuplicateCharacterInSet.ql b/python/ql/src/Expressions/Regex/DuplicateCharacterInSet.ql new file mode 100644 index 000000000000..88c265fb370c --- /dev/null +++ b/python/ql/src/Expressions/Regex/DuplicateCharacterInSet.ql @@ -0,0 +1,34 @@ +/** + * @name Duplication in regular expression character class + * @description Duplicate characters in a class have no effect and may indicate an error in the regular expression. + * @kind problem + * @tags reliability + * readability + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/regex/duplicate-in-character-class + */ + +import python +import semmle.python.regex + +predicate duplicate_char_in_class(Regex r, string char) { + exists(int i, int j, int x, int y, int start, int end | + i != x and j != y and + start < i and j < end and + start < x and y < end and + r.character(i, j) and char = r.getText().substring(i, j) and + r.character(x, y) and char = r.getText().substring(x, y) and + r.charSet(start, end) + ) and + /* Exclude � as we use it for any unencodable character */ + char != "�" and + //Ignore whitespace in verbose mode + not (r.getAMode() = "VERBOSE" and (char = " " or char = "\t" or char = "\r" or char = "\n")) +} + +from Regex r, string char +where duplicate_char_in_class(r, char) +select r, "This regular expression includes duplicate character '" + char + "' in a set of characters." + diff --git a/python/ql/src/Expressions/Regex/MissingPartSpecialGroup.py b/python/ql/src/Expressions/Regex/MissingPartSpecialGroup.py new file mode 100644 index 000000000000..29580414b5b1 --- /dev/null +++ b/python/ql/src/Expressions/Regex/MissingPartSpecialGroup.py @@ -0,0 +1,10 @@ +import re +matcher = re.compile(r'(P[\w]+)') + +def only_letters(text): + m = matcher.match(text) + if m: + print("Letters are: " + m.group('name')) + +#Fix the pattern by adding the missing '?' +fixed_matcher = re.compile(r'(?P[\w]+)') \ No newline at end of file diff --git a/python/ql/src/Expressions/Regex/MissingPartSpecialGroup.qhelp b/python/ql/src/Expressions/Regex/MissingPartSpecialGroup.qhelp new file mode 100644 index 000000000000..289bd4622b99 --- /dev/null +++ b/python/ql/src/Expressions/Regex/MissingPartSpecialGroup.qhelp @@ -0,0 +1,37 @@ + + + +

    +One of the problems with using regular expressions is that almost any sequence of characters is a valid pattern. +This means that it is easy to omit a necessary character and still have a valid regular expression. +Omitting a character in a named capturing group is a specific case which can dramatically change the meaning of a regular expression. +

    + +
    + + +

    +Examine the regular expression to find and correct any typos. +

    + +
    + +

    +In the following example, the regular expression for matcher, r"(P<name>[\w]+)", is missing a "?" and will +match only strings of letters that start with "P<name>", instead of matching any sequence of letters +and placing the result in a named group. +The fixed version, fixed_matcher, includes the "?" and will work as expected. +

    + + + +
    + + +
  • Python Standard Library: Regular expression operations.
  • +
  • Regular-Expressions.info: Named Capturing Groups.
  • + +
    +
    diff --git a/python/ql/src/Expressions/Regex/MissingPartSpecialGroup.ql b/python/ql/src/Expressions/Regex/MissingPartSpecialGroup.ql new file mode 100644 index 000000000000..7a1974fc5147 --- /dev/null +++ b/python/ql/src/Expressions/Regex/MissingPartSpecialGroup.ql @@ -0,0 +1,20 @@ +/** + * @name Missing part of special group in regular expression + * @description Incomplete special groups are parsed as normal groups and are unlikely to match the intended strings. + * @kind problem + * @tags reliability + * correctness + * @problem.severity warning + * @sub-severity high + * @precision high + * @id py/regex/incomplete-special-group + */ + +import python +import semmle.python.regex + +from Regex r, string missing, string part +where r.getText().regexpMatch(".*\\(P<\\w+>.*") and missing = "?" and part = "named group" +select r, "Regular expression is missing '" + missing + "' in " + part + "." + + diff --git a/python/ql/src/Expressions/Regex/UnmatchableCaret.py b/python/ql/src/Expressions/Regex/UnmatchableCaret.py new file mode 100644 index 000000000000..7a51c4a8f932 --- /dev/null +++ b/python/ql/src/Expressions/Regex/UnmatchableCaret.py @@ -0,0 +1,11 @@ +import re +#Regular expression includes a caret, but not at the start. +matcher = re.compile(r"\[^.]*\.css") + +def find_css(filename): + if matcher.match(filename): + print("Found it!") + +#Regular expression for a css file name +fixed_matcher_css = re.compile(r"[^.]*\.css") + diff --git a/python/ql/src/Expressions/Regex/UnmatchableCaret.qhelp b/python/ql/src/Expressions/Regex/UnmatchableCaret.qhelp new file mode 100644 index 000000000000..32914e64a60a --- /dev/null +++ b/python/ql/src/Expressions/Regex/UnmatchableCaret.qhelp @@ -0,0 +1,40 @@ + + + +

    +The caret character ^ anchors a regular expression to the beginning of the input, or +(for multi-line regular expressions) to the beginning of a line. +If it is preceded by a pattern that must match a non-empty sequence of (non-newline) input characters, +then the entire regular expression cannot match anything. +

    + +
    + + +

    +Examine the regular expression to find and correct any typos. +

    + +
    + +

    +In the following example, the regular expression r"\[^.]*\.css" cannot match any +string, since it contains a caret assertion preceded by an escape sequence that matches an +opening bracket. +

    +

    +In the second regular expression, r"[^.]*\.css", the caret is part of a character class, and will not match the start of the string. +

    + + + +
    + + +
  • Python Standard Library: Regular expression operations.
  • +
  • Regular-Expressions.info: Start of String and End of String Anchors.
  • + +
    +
    diff --git a/python/ql/src/Expressions/Regex/UnmatchableCaret.ql b/python/ql/src/Expressions/Regex/UnmatchableCaret.ql new file mode 100644 index 000000000000..7fc0c6f219ee --- /dev/null +++ b/python/ql/src/Expressions/Regex/UnmatchableCaret.ql @@ -0,0 +1,25 @@ +/** + * @name Unmatchable caret in regular expression + * @description Regular expressions containing a caret '^' in the middle cannot be matched, whatever the input. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/regex/unmatchable-caret + */ + +import python +import semmle.python.regex + +predicate unmatchable_caret(Regex r, int start) { + not r.getAMode() = "MULTILINE" and + not r.getAMode() = "VERBOSE" and + r.specialCharacter(start, start+1, "^") and + not r.firstItem(start, start+1) +} + +from Regex r, int offset +where unmatchable_caret(r, offset) +select r, "This regular expression includes an unmatchable caret at offset " + offset.toString() + "." diff --git a/python/ql/src/Expressions/Regex/UnmatchableDollar.py b/python/ql/src/Expressions/Regex/UnmatchableDollar.py new file mode 100644 index 000000000000..8e7a19eb4c1b --- /dev/null +++ b/python/ql/src/Expressions/Regex/UnmatchableDollar.py @@ -0,0 +1,10 @@ +import re +#Regular expression that includes a dollar, but not at the end. +matcher = re.compile(r"\.\(\w+$\)") + +def find_it(filename): + if matcher.match(filename): + print("Found it!") + +#Regular expression anchored to end of input. +fixed_matcher = re.compile(r"\.\(\w+\)$") \ No newline at end of file diff --git a/python/ql/src/Expressions/Regex/UnmatchableDollar.qhelp b/python/ql/src/Expressions/Regex/UnmatchableDollar.qhelp new file mode 100644 index 000000000000..2ff1071430f6 --- /dev/null +++ b/python/ql/src/Expressions/Regex/UnmatchableDollar.qhelp @@ -0,0 +1,41 @@ + + + +

    +A dollar assertion $ in a regular expression only matches at the end of the input, or +(for multi-line regular expressions) at the end of a line. If it is followed by a pattern +that must match a non-empty sequence of (non-newline) input characters, it cannot possibly match, +rendering the entire regular expression unmatchable. +

    + +
    + + +

    +Examine the regular expression to find and correct any typos. +

    + +
    + +

    +In the following example, the regular expression r"\.\(\w+$\)" cannot match any +string, since it contains a dollar assertion followed by an escape sequence that matches a +closing parenthesis. +

    + +

    +The second regular expression, r"\.\(\w+\)$", has the dollar at the end and will work as expected. +

    + + + +
    + + +
  • Python Standard Library: Regular expression operations.
  • +
  • Regular-Expressions.info: Start of String and End of String Anchors.
  • + +
    +
    diff --git a/python/ql/src/Expressions/Regex/UnmatchableDollar.ql b/python/ql/src/Expressions/Regex/UnmatchableDollar.ql new file mode 100644 index 000000000000..49cef2bded12 --- /dev/null +++ b/python/ql/src/Expressions/Regex/UnmatchableDollar.ql @@ -0,0 +1,26 @@ +/** + * @name Unmatchable dollar in regular expression + * @description Regular expressions containing a dollar '$' in the middle cannot be matched, whatever the input. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/regex/unmatchable-dollar + */ + +import python +import semmle.python.regex + +predicate unmatchable_dollar(Regex r, int start) { + not r.getAMode() = "MULTILINE" and + not r.getAMode() = "VERBOSE" and + r.specialCharacter(start, start+1, "$") + and + not r.lastItem(start, start+1) +} + +from Regex r, int offset +where unmatchable_dollar(r, offset) +select r, "This regular expression includes an unmatchable dollar at offset " + offset.toString() + "." diff --git a/python/ql/src/Expressions/TruncatedDivision.py b/python/ql/src/Expressions/TruncatedDivision.py new file mode 100644 index 000000000000..63ed31a8663b --- /dev/null +++ b/python/ql/src/Expressions/TruncatedDivision.py @@ -0,0 +1,7 @@ +# Incorrect: + +def average(l): + return sum(l) / len(l) + +print average([1.0, 2.0]) # Prints "1.5". +print average([1, 2]) # Prints "1", which is incorrect. diff --git a/python/ql/src/Expressions/TruncatedDivision.qhelp b/python/ql/src/Expressions/TruncatedDivision.qhelp new file mode 100644 index 000000000000..1daa6f7fef6e --- /dev/null +++ b/python/ql/src/Expressions/TruncatedDivision.qhelp @@ -0,0 +1,42 @@ + + + +

    + In Python 2, the result of dividing two integers is silently truncated into an integer. This may lead to unexpected behavior. +

    + +
    + + +

    + If the division should never be truncated, add + from __future__ import division + to the beginning of the file. If the division should always + be truncated, replace the division operator / with the + truncated division operator //. +

    + +
    + +

    + The first example shows a function for calculating the average of a sequence + of numbers. When the function runs under Python 2, and the sequence contains + only integers, an incorrect result may be returned because the result is + truncated. The second example corrects this error by following the + recommendation listed above. +

    + + + + + +
    + + +
  • Python Language Reference: Binary arithmetic operations.
  • +
  • PEP 238: Changing the Division Operator.
  • +
  • PEP 236: Back to the __future__.
  • +
    +
    diff --git a/python/ql/src/Expressions/TruncatedDivision.ql b/python/ql/src/Expressions/TruncatedDivision.ql new file mode 100644 index 000000000000..3d4deb9ba540 --- /dev/null +++ b/python/ql/src/Expressions/TruncatedDivision.ql @@ -0,0 +1,37 @@ + /** + * @name Result of integer division may be truncated + * @description The arguments to a division statement may be integers, which + * may cause the result to be truncated in Python 2. + * @kind problem + * @tags maintainability + * correctness + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/truncated-division + */ + +import python + +from BinaryExpr div, ControlFlowNode left, ControlFlowNode right +where + // Only relevant for Python 2, as all later versions implement true division + major_version() = 2 + and + exists(BinaryExprNode bin, Object lobj, Object robj | + bin = div.getAFlowNode() + and bin.getNode().getOp() instanceof Div + and bin.getLeft().refersTo(lobj, theIntType(), left) + and bin.getRight().refersTo(robj, theIntType(), right) + // Ignore instances where integer division leaves no remainder + and not lobj.(NumericObject).intValue() % robj.(NumericObject).intValue() = 0 + and not bin.getNode().getEnclosingModule().hasFromFuture("division") + // Filter out results wrapped in `int(...)` + and not exists(CallNode c, ClassObject cls | + c.getAnArg() = bin + and c.getFunction().refersTo(cls) + and cls.getName() = "int" + ) + ) +select div, "Result of division may be truncated as its $@ and $@ arguments may both be integers.", + left.getLocation(), "left", right.getLocation(), "right" diff --git a/python/ql/src/Expressions/TruncatedDivisionCorrect.py b/python/ql/src/Expressions/TruncatedDivisionCorrect.py new file mode 100644 index 000000000000..eb51454bb860 --- /dev/null +++ b/python/ql/src/Expressions/TruncatedDivisionCorrect.py @@ -0,0 +1,8 @@ +# Correct: +from __future__ import division + +def average(l): + return sum(l) / len(l) + +print average([1.0, 2.0]) # Prints "1.5". +print average([1, 2]) # Prints "1.5". diff --git a/python/ql/src/Expressions/UnintentionalImplicitStringConcatenation.py b/python/ql/src/Expressions/UnintentionalImplicitStringConcatenation.py new file mode 100644 index 000000000000..7bc0862ae9b7 --- /dev/null +++ b/python/ql/src/Expressions/UnintentionalImplicitStringConcatenation.py @@ -0,0 +1,19 @@ + +def unclear(): + # Returns [ "first part of long string and the second part", "/usr/local/usr/bin" ] + return [ + + "first part of long string" + " and the second part", + "/usr/local" + "/usr/bin" + ] + +def clarified(): + # Returns [ "first part of long string and the second part", "/usr/local", "/usr/bin" ] + return [ + "first part of long string" + + " and the second part", + "/usr/local", + "/usr/bin" + ] diff --git a/python/ql/src/Expressions/UnintentionalImplicitStringConcatenation.qhelp b/python/ql/src/Expressions/UnintentionalImplicitStringConcatenation.qhelp new file mode 100644 index 000000000000..281d684224eb --- /dev/null +++ b/python/ql/src/Expressions/UnintentionalImplicitStringConcatenation.qhelp @@ -0,0 +1,39 @@ + + + + +

    When two string literals abut each other the Python interpreter implicitly concatenates them into a +single string. On occasion this can be useful, but is more commonly misleading or incorrect. +

    + +
    + + + +

    If the concatenation is deliberate, then use + to join the strings. This has no runtime overhead, +and makes the intention clear. +

    + + +
    + + +

    +In the first function below, unclear, implicit string concatenation is used twice; once deliberately and once by accident. +In the second function, clarified, the first concatenation is made explicit and the second is removed. +

    + + + + +
    + + + + +
  • Python language reference: String literal concatenation.
  • + +
    +
    diff --git a/python/ql/src/Expressions/UnintentionalImplicitStringConcatenation.ql b/python/ql/src/Expressions/UnintentionalImplicitStringConcatenation.ql new file mode 100644 index 000000000000..701284069152 --- /dev/null +++ b/python/ql/src/Expressions/UnintentionalImplicitStringConcatenation.ql @@ -0,0 +1,35 @@ +/** + * @name Implicit string concatenation in a list + * @description Omitting a comma between strings causes implicit concatenation which is confusing in a list. + * @kind problem + * @tags reliability + * maintainability + * convention + * external/cwe/cwe-665 + * @problem.severity warning + * @sub-severity high + * @precision high + * @id py/implicit-string-concatenation-in-list + */ + +import python + +predicate string_const(Expr s) { + s instanceof StrConst + or + string_const(s.(BinaryExpr).getLeft()) and string_const(s.(BinaryExpr).getRight()) +} + +from StrConst s +where +// Implicitly concatenated string is in a list and that list contains at least one other string. +exists(List l, Expr other | + not s = other and + l.getAnElt() = s and + l.getAnElt() = other and + string_const(other) +) and +exists(s.getAnImplicitlyConcatenatedPart()) and +not s.isParenthesized() + +select s, "Implicit string concatenation. Maybe missing a comma?" diff --git a/python/ql/src/Expressions/UnnecessaryLambda.py b/python/ql/src/Expressions/UnnecessaryLambda.py new file mode 100644 index 000000000000..7c296bfceab6 --- /dev/null +++ b/python/ql/src/Expressions/UnnecessaryLambda.py @@ -0,0 +1,7 @@ +import math + +def call_with_x_squared(x, function): + x = x*x + return function(x) + +print call_with_x_squared(2, lambda x: math.factorial(x)) \ No newline at end of file diff --git a/python/ql/src/Expressions/UnnecessaryLambda.qhelp b/python/ql/src/Expressions/UnnecessaryLambda.qhelp new file mode 100644 index 000000000000..04ca0174b076 --- /dev/null +++ b/python/ql/src/Expressions/UnnecessaryLambda.qhelp @@ -0,0 +1,29 @@ + + + +

    A lambda that calls a function without modifying any of its parameters is unnecessary. +Python functions are first class objects and can be passed around in the same way as the resulting lambda. +

    + +
    + +

    Remove the lambda, use the function directly.

    + +
    + +

    In this example a lambda is used unnecessarily in order to pass a method as an argument to +call_with_x_squared.

    + + +

    This is not necessary as methods can be passed directly. They behave as callable objects.

    + + +
    + + +
  • Python: lambdas.
  • + +
    +
    diff --git a/python/ql/src/Expressions/UnnecessaryLambda.ql b/python/ql/src/Expressions/UnnecessaryLambda.ql new file mode 100644 index 000000000000..93b78238e9a8 --- /dev/null +++ b/python/ql/src/Expressions/UnnecessaryLambda.ql @@ -0,0 +1,57 @@ +/** + * @name Unnecessary lambda + * @description A lambda is used that calls through to a function without modifying any parameters + * @kind problem + * @tags maintainability + * useless-code + * @problem.severity recommendation + * @sub-severity high + * @precision high + * @id py/unnecessary-lambda + */ + +import python + +/* f consists of a single return statement, whose value is a call. The arguments of the call are exactly the parameters of f */ +predicate simple_wrapper(Lambda l, Expr wrapped) { + exists(Function f, Call c | f = l.getInnerScope() and c = l.getExpression() | + wrapped = c.getFunc() and + count(f.getAnArg()) = count(c.getAnArg()) and + forall(int arg | exists(f.getArg(arg)) | + f.getArgName(arg) = ((Name)c.getArg(arg)).getId()) and + /* Either no **kwargs or they must match */ + (not exists(f.getKwarg()) and not exists(c.getKwargs()) or + ((Name)f.getKwarg()).getId() = ((Name)c.getKwargs()).getId()) and + /* Either no *args or they must match */ + (not exists(f.getVararg()) and not exists(c.getStarargs()) or + ((Name)f.getVararg()).getId() = ((Name)c.getStarargs()).getId()) and + /* No named parameters in call */ + not exists(c.getAKeyword()) + ) + and + // f is not necessarily a drop-in replacement for the lambda if there are default argument values + not exists(l.getArgs().getADefault()) +} + +/* The expression called will refer to the same object if evaluated when the lambda is created or when the lambda is executed. */ +predicate unnecessary_lambda(Lambda l, Expr e) { + simple_wrapper(l, e) and + ( + /* plain class */ + exists(ClassObject c | e.refersTo(c)) + or + /* plain function */ + exists(FunctionObject f | e.refersTo(f)) + or + /* bound-method of enclosing instance */ + exists(ClassObject cls, Attribute a | + cls.getPyClass() = l.getScope().getScope() and a = e | + ((Name)a.getObject()).getId() = "self" and + cls.hasAttribute(a.getName()) + ) + ) +} + +from Lambda l, Expr e +where unnecessary_lambda(l, e) +select l, "This 'lambda' is just a simple wrapper around a callable object. Use that object directly." \ No newline at end of file diff --git a/python/ql/src/Expressions/UnnecessaryLambdaFix.py b/python/ql/src/Expressions/UnnecessaryLambdaFix.py new file mode 100644 index 000000000000..bbfcdb98aa06 --- /dev/null +++ b/python/ql/src/Expressions/UnnecessaryLambdaFix.py @@ -0,0 +1,7 @@ +import math + +def call_with_x_squared(x, function): + x = x*x + return function(x) + +print call_with_x_squared(2, math.factorial) \ No newline at end of file diff --git a/python/ql/src/Expressions/UnsupportedFormatCharacter.py b/python/ql/src/Expressions/UnsupportedFormatCharacter.py new file mode 100644 index 000000000000..60c4ebc53b94 --- /dev/null +++ b/python/ql/src/Expressions/UnsupportedFormatCharacter.py @@ -0,0 +1,6 @@ + +def format_as_tuple_incorrect(args): + return "%t" % args + +def format_as_tuple_correct(args): + return "%r" % (args,) diff --git a/python/ql/src/Expressions/UnsupportedFormatCharacter.qhelp b/python/ql/src/Expressions/UnsupportedFormatCharacter.qhelp new file mode 100644 index 000000000000..b22d59a209c5 --- /dev/null +++ b/python/ql/src/Expressions/UnsupportedFormatCharacter.qhelp @@ -0,0 +1,28 @@ + + + +

    A format string, that is the string on the left hand side of an expression like fmt % arguments, must consist of legal conversion specifiers. +Otherwise, a ValueError will be raised. + +

    + +
    + +

    Choose a legal conversion specifier.

    + +
    + +

    In format_as_tuple_incorrect, "t" is not a legal conversion specifier. + +

    + + +
    + + +
  • Python Library Reference: String Formatting.
  • + +
    +
    diff --git a/python/ql/src/Expressions/UnsupportedFormatCharacter.ql b/python/ql/src/Expressions/UnsupportedFormatCharacter.ql new file mode 100644 index 000000000000..d3876725233a --- /dev/null +++ b/python/ql/src/Expressions/UnsupportedFormatCharacter.ql @@ -0,0 +1,18 @@ +/** + * @name Unsupported format character + * @description An unsupported format character in a format string + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/percent-format/unsupported-character + */ + +import python +import semmle.python.strings + +from Expr e, int start +where start = illegal_conversion_specifier(e) +select e, "Invalid conversion specifier at index " + start + " of " + repr(e) + "." diff --git a/python/ql/src/Expressions/UseofApply.qhelp b/python/ql/src/Expressions/UseofApply.qhelp new file mode 100644 index 000000000000..afc4e7dea1e0 --- /dev/null +++ b/python/ql/src/Expressions/UseofApply.qhelp @@ -0,0 +1,28 @@ + + + + +

    The 'apply' function is deprecated and makes code harder to read as most Python programmers +will not be familiar with it (it has been deprecated since 2003). +

    + +
    + + +

    Replace apply(function, args) with function(*args). +

    +Replace apply(function, args, keywords) with function(*args, **keywords). +

    + + +
    + + +
  • Python Standard Library: apply.
  • +
  • Python PEP-290: Code Migration and Modernization.
  • + + +
    +
    diff --git a/python/ql/src/Expressions/UseofApply.ql b/python/ql/src/Expressions/UseofApply.ql new file mode 100644 index 000000000000..f9419962c29c --- /dev/null +++ b/python/ql/src/Expressions/UseofApply.ql @@ -0,0 +1,17 @@ +/** + * @name 'apply' function used + * @description The builtin function 'apply' is obsolete and should not be used. + * @kind problem + * @tags maintainability + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/use-of-apply + */ + +import python + +from CallNode call, ControlFlowNode func +where +major_version() = 2 and call.getFunction() = func and func.refersTo(theApplyFunction()) +select call, "Call to the obsolete builtin function 'apply'." diff --git a/python/ql/src/Expressions/UseofInput.qhelp b/python/ql/src/Expressions/UseofInput.qhelp new file mode 100644 index 000000000000..44baace8c43b --- /dev/null +++ b/python/ql/src/Expressions/UseofInput.qhelp @@ -0,0 +1,23 @@ + + + +

    A call to the input() function, input(prompt) is equivalent to eval(raw_input(prompt)). Evaluating user input without any checking can be a serious security flaw.

    + +
    + + +

    Get user input with raw_input(prompt) and then validate that input before evaluating. If the expected input is a number or +string, then ast.literal_eval() can always be used safely.

    + + +
    + + +
  • Python Standard Library: input, + ast.literal_eval.
  • +
  • Wikipedia: Data validation.
  • + +
    +
    diff --git a/python/ql/src/Expressions/UseofInput.ql b/python/ql/src/Expressions/UseofInput.ql new file mode 100644 index 000000000000..39289dd8a844 --- /dev/null +++ b/python/ql/src/Expressions/UseofInput.ql @@ -0,0 +1,18 @@ +/** + * @name 'input' function used + * @description The built-in function 'input' is used which can allow arbitrary code to be run. + * @kind problem + * @tags security + * correctness + * @problem.severity error + * @sub-severity high + * @precision high + * @id py/use-of-input + */ + +import python + +from CallNode call, Context context, ControlFlowNode func +where +context.getAVersion().includes(2, _) and call.getFunction() = func and func.refersTo(context, theInputFunction(), _, _) +select call, "The unsafe built-in function 'input' is used." diff --git a/python/ql/src/Expressions/WrongNameForArgumentInCall.qhelp b/python/ql/src/Expressions/WrongNameForArgumentInCall.qhelp new file mode 100644 index 000000000000..79be173c1077 --- /dev/null +++ b/python/ql/src/Expressions/WrongNameForArgumentInCall.qhelp @@ -0,0 +1,30 @@ + + + +

    +Using a named argument whose name does not correspond to a parameter of the called function (or method), will result in a +TypeError at runtime. +

    + +
    + + +

    Check for typos in the name of the arguments and fix those. +If the name is clearly different, then this suggests a logical error. +The change required to correct the error will depend on whether the wrong argument has been +specified or whether the wrong function (or method) has been specified. +

    + +
    + + +
  • Python Glossary: Arguments.
  • +
  • Python Glossary: Parameters.
  • +
  • Python Programming FAQ: + What is the difference between arguments and parameters?.
  • + + +
    +
    diff --git a/python/ql/src/Expressions/WrongNameForArgumentInCall.ql b/python/ql/src/Expressions/WrongNameForArgumentInCall.ql new file mode 100644 index 000000000000..92c17f8e2ee4 --- /dev/null +++ b/python/ql/src/Expressions/WrongNameForArgumentInCall.ql @@ -0,0 +1,26 @@ +/** + * @name Wrong name for an argument in a call + * @description Using a named argument whose name does not correspond to a + * parameter of the called function or method, will result in a + * TypeError at runtime. + * @kind problem + * @tags reliability + * correctness + * external/cwe/cwe-628 + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/call/wrong-named-argument + */ + +import python +import Expressions.CallArgs + + +from Call call, FunctionObject func, string name +where +illegally_named_parameter(call, func, name) and +not func.isAbstract() and +not exists(FunctionObject overridden | func.overrides(overridden) and overridden.getFunction().getAnArg().(Name).getId() = name) +select +call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", func, func.descriptiveString() diff --git a/python/ql/src/Expressions/WrongNumberArgumentsForFormat.py b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.py new file mode 100644 index 000000000000..20ab7f15dab8 --- /dev/null +++ b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.py @@ -0,0 +1,6 @@ +def unsafe_format(): + if unlikely_condition(): + args = (1,2) + else: + args = (1, 2, 3) + return "%s %s %s" % args diff --git a/python/ql/src/Expressions/WrongNumberArgumentsForFormat.qhelp b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.qhelp new file mode 100644 index 000000000000..00d5cebe701b --- /dev/null +++ b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.qhelp @@ -0,0 +1,26 @@ + + + +

    A formatting expression, that is an expression of the format fmt % arguments must have the correct number of +arguments on the right hand side of the expression. Otherwise, a TypeError will be raised. + +

    + +
    + +

    Change the format to match the arguments and ensure that the right hand argument always has the correct number of elements. + +

    + +

    In the following example the right hand side of the formatting operation can be of length 2, which does not match the format string<./p> +

    + +
    + + +
  • Python Library Reference: String Formatting.
  • + +
    +
    diff --git a/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql new file mode 100644 index 000000000000..aa163d915444 --- /dev/null +++ b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql @@ -0,0 +1,45 @@ +/** + * @name Wrong number of arguments for format + * @description A string formatting operation, such as '"%s: %s, %s" % (a,b)', where the number of conversion specifiers in the + * format string differs from the number of values to be formatted will raise a TypeError. + * @kind problem + * @tags reliability + * correctness + * external/cwe/cwe-685 + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/percent-format/wrong-arguments + */ + +import python +import semmle.python.strings + +predicate string_format(BinaryExpr operation, StrConst str, Object args, AstNode origin) { + exists(Object fmt, Context ctx | operation.getOp() instanceof Mod | + operation.getLeft().refersTo(ctx, fmt, _, str) and + operation.getRight().refersTo(ctx, args, _, origin) + ) +} + +int sequence_length(Object args) { + /* Guess length of sequence */ + exists(Tuple seq | + seq = args.getOrigin() | + result = strictcount(seq.getAnElt()) and + not seq.getAnElt() instanceof Starred + ) + or + exists(ImmutableLiteral i | + i.getLiteralObject() = args | + result = 1 + ) +} + + +from BinaryExpr operation, StrConst fmt, Object args, int slen, int alen, AstNode origin, string provided +where string_format(operation, fmt, args, origin) and slen = sequence_length(args) and alen = format_items(fmt) and slen != alen and +(if slen = 1 then provided = " is provided." else provided = " are provided.") +select operation, "Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " + slen.toString() + provided, + origin, "arguments", + fmt, fmt.getText() diff --git a/python/ql/src/Expressions/WrongNumberArgumentsInCall.qhelp b/python/ql/src/Expressions/WrongNumberArgumentsInCall.qhelp new file mode 100644 index 000000000000..6215a3c35e95 --- /dev/null +++ b/python/ql/src/Expressions/WrongNumberArgumentsInCall.qhelp @@ -0,0 +1,39 @@ + + + +

    + A function call must supply an argument for each parameter that does not have a default value defined, so: +

    +
      +
    • The minimum number of arguments is the number of parameters without default values.
    • +
    • The maximum number of arguments is the total number of parameters, + unless the function takes a varargs (starred) parameter in which case there + is no limit.
    • +
    +
    + +

    If there are too few arguments then check to see which arguments have been omitted and supply values for those.

    + +

    If there are too many arguments then check to see if any have been added by mistake and remove those.

    + +

    + Also check where a comma has been inserted instead of an operator or a dot. + For example, the code is obj,attr when it should be obj.attr. +

    +

    If it is not clear which are the missing or surplus arguments, then this suggests a logical error. +The fix will then depend on the nature of the error. +

    + +
    + + +
  • Python Glossary: Arguments.
  • +
  • Python Glossary: Parameters.
  • +
  • Python Programming FAQ: + What is the difference between arguments and parameters?.
  • + + +
    +
    diff --git a/python/ql/src/Expressions/WrongNumberArgumentsInCall.ql b/python/ql/src/Expressions/WrongNumberArgumentsInCall.ql new file mode 100644 index 000000000000..b31e9e704453 --- /dev/null +++ b/python/ql/src/Expressions/WrongNumberArgumentsInCall.ql @@ -0,0 +1,30 @@ +/** + * @name Wrong number of arguments in a call + * @description Using too many or too few arguments in a call to a function will result in a TypeError at runtime. + * @kind problem + * @tags reliability + * correctness + * external/cwe/cwe-685 + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/call/wrong-arguments + */ + +import python +import CallArgs + +from Call call, FunctionObject func, string too, string should, int limit +where +( + too_many_args(call, func, limit) and too = "too many arguments" and should = "no more than " + or + too_few_args(call, func, limit) and too = "too few arguments" and should = "no fewer than " +) and +not func.isAbstract() and +not exists(FunctionObject overridden | func.overrides(overridden) and correct_args_if_called_as_method(call, overridden)) +/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */ +and not func.getName() = "__new__" + +select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func, func.descriptiveString() + diff --git a/python/ql/src/Filters/ClassifyFiles.ql b/python/ql/src/Filters/ClassifyFiles.ql new file mode 100644 index 000000000000..4448d993903f --- /dev/null +++ b/python/ql/src/Filters/ClassifyFiles.ql @@ -0,0 +1,20 @@ +/** + * @name Classify files + * @description This query produces a list of all files in a snapshot + * that are classified as generated code or test code. + * @kind file-classifier + * @id py/file-classifier + */ + +import python +import semmle.python.filters.GeneratedCode +import semmle.python.filters.Tests + +predicate classify(File f, string tag) { + f instanceof GeneratedFile and tag = "generated" or + exists (TestScope t | t.getLocation().getFile() = f) and tag = "test" +} + +from File f, string tag +where classify(f, tag) +select f, tag diff --git a/python/ql/src/Filters/ImportAdditionalLibraries.ql b/python/ql/src/Filters/ImportAdditionalLibraries.ql new file mode 100644 index 000000000000..aa55f486747a --- /dev/null +++ b/python/ql/src/Filters/ImportAdditionalLibraries.ql @@ -0,0 +1,16 @@ +/** + * @name (Import additional libraries) + * @description This query produces no results but imports some libraries we + * would like to make available in the LGTM query console even + * if they are not used by any queries. + * @kind file-classifier + * @id py/lgtm/import-additional-libraries + */ + +private import external.CodeDuplication +private import external.Thrift +private import external.VCS + +from File f, string tag +where none() +select f, tag diff --git a/python/ql/src/Filters/NotGenerated.ql b/python/ql/src/Filters/NotGenerated.ql new file mode 100644 index 000000000000..121fc3c7a458 --- /dev/null +++ b/python/ql/src/Filters/NotGenerated.ql @@ -0,0 +1,12 @@ +/** + * @name Filter: non-generated files + * @description Only keep results that aren't (or don't appear to be) generated. + * @kind file-classifier + * @id py/not-generated-file-filter + */ +import external.DefectFilter +import semmle.python.filters.GeneratedCode + +from DefectResult res +where not exists(GeneratedFile f | res.getFile() = f) +select res, res.getMessage() diff --git a/python/ql/src/Filters/NotTest.ql b/python/ql/src/Filters/NotTest.ql new file mode 100644 index 000000000000..4d6b0ec51622 --- /dev/null +++ b/python/ql/src/Filters/NotTest.ql @@ -0,0 +1,12 @@ +/** + * @name Filter: non-test files + * @description Only keep results that aren't in tests + * @kind file-classifier + * @id py/not-test-file-filter + */ +import external.DefectFilter +import semmle.python.filters.Tests + +from DefectResult res +where not exists(TestScope s | contains(s.getLocation(), res)) +select res, res.getMessage() diff --git a/python/ql/src/Functions/ConsistentReturns.py b/python/ql/src/Functions/ConsistentReturns.py new file mode 100644 index 000000000000..8a15b8655743 --- /dev/null +++ b/python/ql/src/Functions/ConsistentReturns.py @@ -0,0 +1,19 @@ + def check_state1(state, interactive=True): + if not state['good'] or not state['bad']: + if (good or bad or skip or reset) and interactive: + return # implicitly return None + if not state['good']: + raise util.Abort(_('cannot bisect (no known good revisions)')) + else: + raise util.Abort(_('cannot bisect (no known bad revisions)')) + return True + + def check_state2(state, interactive=True): + if not state['good'] or not state['bad']: + if (good or bad or skip or reset) and interactive: + return False # return an explicit value + if not state['good']: + raise util.Abort(_('cannot bisect (no known good revisions)')) + else: + raise util.Abort(_('cannot bisect (no known bad revisions)')) + return True diff --git a/python/ql/src/Functions/ConsistentReturns.qhelp b/python/ql/src/Functions/ConsistentReturns.qhelp new file mode 100644 index 000000000000..cd29062ada66 --- /dev/null +++ b/python/ql/src/Functions/ConsistentReturns.qhelp @@ -0,0 +1,37 @@ + + + + + +

    When a function contains both explicit returns (return value) and implicit returns +(where code falls off the end of a function) this often indicates that a return +statement has been forgotten. It is best to return an explicit return value even when returning +None because this makes it easier for other developers to read your code. +

    + +
    + + +

    Add an explicit return at the end of the function.

    + + +
    + +

    In the check_state1 function, the developer probably did intend to use an implicit +return value of None as this equates to False. However, the function in +check_state2 is easier to read.

    + + + + +
    + + +
  • Python Language Reference: Function definitions. +
  • + + +
    +
    diff --git a/python/ql/src/Functions/ConsistentReturns.ql b/python/ql/src/Functions/ConsistentReturns.ql new file mode 100644 index 000000000000..f3344bd8f748 --- /dev/null +++ b/python/ql/src/Functions/ConsistentReturns.ql @@ -0,0 +1,32 @@ +/** + * @name Explicit returns mixed with implicit (fall through) returns + * @description Mixing implicit and explicit returns indicates a likely error as implicit returns always return 'None'. + * @kind problem + * @tags reliability + * maintainability + * @problem.severity recommendation + * @sub-severity high + * @precision high + * @id py/mixed-returns + */ + +import python + +predicate explicitly_returns_non_none(Function func) { + exists(Return return | return.getScope() = func and + exists(Expr val | + val= return.getValue() | + not val instanceof None + ) + ) +} + +predicate has_implicit_return(Function func) { + exists(ControlFlowNode fallthru | fallthru = func.getFallthroughNode() and not fallthru.unlikelyReachable()) or + exists(Return return | return.getScope() = func and not exists(return.getValue())) +} + + +from Function func +where explicitly_returns_non_none(func) and has_implicit_return(func) +select func, "Mixing implicit and explicit returns may indicate an error as implicit returns always return None." diff --git a/python/ql/src/Functions/DeprecatedSliceMethod.qhelp b/python/ql/src/Functions/DeprecatedSliceMethod.qhelp new file mode 100644 index 000000000000..9a47eeaf3275 --- /dev/null +++ b/python/ql/src/Functions/DeprecatedSliceMethod.qhelp @@ -0,0 +1,37 @@ + + + +

    The __getslice__, __setslice__ and __delslice__ methods have been deprecated since Python 2.0. +In general, no class should implement these methods. +

    + +

    +The only exceptions to this rule are classes that inherit from list and override __getitem__, +__setitem__ or __delitem__. +Since list implements the slicing methods any class inheriting from list must implement the +the slicing methods to ensure correct behavior of __getitem__, __setitem__ and __delitem__. +These exceptions to the rule will not be treated as violations. +

    + +
    + +

    +Delete the slicing method. Any functionality should be moved to the equivalent __xxxitem__ method: +

    +
      +
    • __getslice__ should be replaced with __getitem__
    • +
    • __setslice__ should be replaced with __setitem__
    • +
    • __delslice__ should be replaced with __delitem__
    • +
    + +
    + + +
  • Python Language Reference: +Additional methods for emulation of sequence types. +
  • + +
    +
    diff --git a/python/ql/src/Functions/DeprecatedSliceMethod.ql b/python/ql/src/Functions/DeprecatedSliceMethod.ql new file mode 100644 index 000000000000..b81d0b750a64 --- /dev/null +++ b/python/ql/src/Functions/DeprecatedSliceMethod.ql @@ -0,0 +1,24 @@ +/** + * @name Deprecated slice method + * @description Defining special methods for slicing has been deprecated since Python 2.0. + * @kind problem + * @tags maintainability + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/deprecated-slice-method + */ + +import python + +predicate slice_method_name(string name) { + name = "__getslice__" or name = "__setslice__" or name = "__delslice__" +} + +from PyFunctionObject f, string meth + +where f.getFunction().isMethod() and not f.isOverridingMethod() and + slice_method_name(meth) and f.getName() = meth + + +select f, meth + " method has been deprecated since Python 2.0" \ No newline at end of file diff --git a/python/ql/src/Functions/ExplicitReturnInInit.py b/python/ql/src/Functions/ExplicitReturnInInit.py new file mode 100644 index 000000000000..b0ab9760d7cb --- /dev/null +++ b/python/ql/src/Functions/ExplicitReturnInInit.py @@ -0,0 +1,4 @@ +class ExplicitReturnInInit(object): + def __init__(self, i): + self.i = i + return self \ No newline at end of file diff --git a/python/ql/src/Functions/ExplicitReturnInInit.qhelp b/python/ql/src/Functions/ExplicitReturnInInit.qhelp new file mode 100644 index 000000000000..c789f24c4e54 --- /dev/null +++ b/python/ql/src/Functions/ExplicitReturnInInit.qhelp @@ -0,0 +1,28 @@ + + + +

    The __init__ method of a class is used to initialize new objects, +not create them. As such, it should not return any value. Returning None +is correct in the sense that no runtime error will occur, +but it suggests that the returned value is meaningful, which it is not.

    + +
    + +

    Convert the return expr statement to a plain return statement, +or omit it altogether if it is at the end of the method.

    + +
    + +

    In this example, the __init__ method attempts to return the newly created +object. This is an error and the return method should be removed.

    + + +
    + + +
  • Python: The __init__ method.
  • + +
    +
    diff --git a/python/ql/src/Functions/ExplicitReturnInInit.ql b/python/ql/src/Functions/ExplicitReturnInInit.ql new file mode 100644 index 000000000000..0885e7cbdd73 --- /dev/null +++ b/python/ql/src/Functions/ExplicitReturnInInit.ql @@ -0,0 +1,23 @@ +/** + * @name __init__ method returns a value + * @description Explicitly returning a value from an __init__ method will raise a TypeError. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/explicit-return-in-init + */ + +import python + +from Return r +where exists(Function init | init.isInitMethod() and +r.getScope() = init and exists(r.getValue())) and +not r.getValue() instanceof None and +not exists(FunctionObject f | f.getACall() = r.getValue().getAFlowNode() | + f.neverReturns() +) and +not exists(Attribute meth | meth = ((Call)r.getValue()).getFunc() | meth.getName() = "__init__") +select r, "Explicit return in __init__ method." diff --git a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.py b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.py new file mode 100644 index 000000000000..e76c27145dbb --- /dev/null +++ b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.py @@ -0,0 +1,16 @@ +#Incorrect unhashable class +class MyMutableThing(object): + + def __init__(self): + pass + + def __hash__(self): + raise NotImplementedError("%r is unhashable" % self) + +#Make class unhashable in the standard way +class MyCorrectMutableThing(object): + + def __init__(self): + pass + + __hash__ = None diff --git a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.qhelp b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.qhelp new file mode 100644 index 000000000000..f4f0cd6920ab --- /dev/null +++ b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.qhelp @@ -0,0 +1,71 @@ + + + +

    User-defined classes interact with the Python virtual machine via special methods (also called "magic methods"). +For example, for a class to support addition it must implement the __add__ and __radd__ special methods. +When the expression a + b is evaluated the Python virtual machine will call type(a).__add__(a, b) and if that +is not implemented it will call type(b).__radd__(b, a).

    +

    +Since the virtual machine calls these special methods for common expressions, users of the class will expect these operations to raise standard exceptions. +For example, users would expect that the expression a.b might raise an AttributeError +if the object a does not have an attribute b. +If a KeyError were raised instead, +then this would be unexpected and may break code that expected an AttributeError, but not a KeyError. +

    + +

    +Therefore, if a method is unable to perform the expected operation then its response should conform to the standard protocol, described below. +

    + +
      +
    • Attribute access, a.b: Raise AttributeError
    • +
    • Arithmetic operations, a + b: Do not raise an exception, return NotImplemented instead.
    • +
    • Indexing, a[b]: Raise KeyError.
    • +
    • Hashing, hash(a): Use __hash__ = None to indicate that an object is unhashable.
    • +
    • Equality methods, a != b: Never raise an exception, always return True or False.
    • +
    • Ordering comparison methods, a < b: Raise a TypeError if the objects cannot be ordered.
    • +
    • Most others: Ideally, do not implement the method at all, otherwise raise TypeError to indicate that the operation is unsupported.
    • +
    + +
    + +

    If the method is meant to be abstract, then declare it so using the @abstractmethod decorator. +Otherwise, either remove the method or ensure that the method raises an exception of the correct type. +

    + +
    + + +

    +This example shows two unhashable classes. The first class is unhashable in a non-standard way which may cause maintenance problems. +The second, corrected, class uses the standard idiom for unhashable classes. +

    + +

    +In this example, the first class is implicitly abstract; the __add__ method is unimplemented, +presumably with the expectation that it will be implemented by sub-classes. +The second class makes this explicit with an @abstractmethod decoration on the unimplemented __add__ method. +

    + +

    +In this last example, the first class implements a collection backed by the file store. +However, should an IOError be raised in the __getitem__ it will propagate to the caller. +The second class handles any IOError by reraising a KeyError which is the standard exception for +the __getitem__ method. +

    + + + + +
    + + +
  • Python Language Reference: Special Method Names.
  • +
  • Python Library Reference: Exceptions.
  • + + + +
    +
    diff --git a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql new file mode 100644 index 000000000000..7d54c0b49ef0 --- /dev/null +++ b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql @@ -0,0 +1,112 @@ +/** + * @name Non-standard exception raised in special method + * @description Raising a non-standard exception in a special method alters the expected interface of that method. + * @kind problem + * @tags reliability + * maintainability + * convention + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/unexpected-raise-in-special-method + */ + +import python + +private predicate attribute_method(string name) { + name = "__getattribute__" or name = "__getattr__" or name = "__setattr__" +} + +private predicate indexing_method(string name) { + name = "__getitem__" or name = "__setitem__" or name = "__delitem__" +} + +private predicate arithmetic_method(string name) { + name = "__add__" or name = "__sub__" or name = "__div__" or + name = "__pos__" or name = "__abs__" or name = "__floordiv__" or + name = "__div__" or name = "__divmod__" or name = "__lshift__" or + name = "__and__" or name = "__or__"or name = "__xor__" or name = "__rshift__" or + name = "__pow__" or name = "__mul__" or name = "__neg__" or + name = "__radd__" or name = "__rsub__" or name = "__rdiv__" or + name = "__rfloordiv__" or name = "__rdiv__" or name = "__rlshift__" or + name = "__rand__" or name = "__ror__"or name = "__rxor__" or name = "__rrshift__" or + name = "__rpow__" or name = "__rmul__" or name = "__truediv__" or name = "__rtruediv__" or + name = "__iadd__" or name = "__isub__" or name = "__idiv__" or + name = "__ifloordiv__" or name = "__idiv__" or name = "__ilshift__" or + name = "__iand__" or name = "__ior__"or name = "__ixor__" or name = "__irshift__" or + name = "__ipow__" or name = "__imul__" or name = "__itruediv__" +} + +private predicate ordering_method(string name) { + name = "__lt__" or name = "__le__" or name = "__gt__" or name = "__ge__" or + name = "__cmp__" and major_version() = 2 +} + +private predicate cast_method(string name) { + name = "__nonzero__" and major_version() = 2 or + name = "__bool__" or + name = "__int__" or name = "__float__" or + name = "__long__" or + name = "__trunc__" or + name = "__complex__" +} + +predicate correct_raise(string name, ClassObject ex) { + ex.getAnImproperSuperType() = theTypeErrorType() + and + ( + name = "__copy__" or + name = "__deepcopy__" or + name = "__call__" or + indexing_method(name) or + attribute_method(name) + ) + or + preferred_raise(name, ex) + or + preferred_raise(name, ex.getASuperType()) +} + +predicate preferred_raise(string name, ClassObject ex) { + attribute_method(name) and ex = theAttributeErrorType() + or + indexing_method(name) and ex = builtin_object("LookupError") + or + ordering_method(name) and ex = theTypeErrorType() + or + arithmetic_method(name) and ex = builtin_object("ArithmeticError") +} + +predicate no_need_to_raise(string name, string message) { + name = "__hash__" and message = "use __hash__ = None instead" + or + cast_method(name) and message = "there is no need to implement the method at all." +} + +predicate is_abstract(FunctionObject func) { + ((Name)func.getFunction().getADecorator()).getId().matches("%abstract%") +} + +predicate always_raises(FunctionObject f, ClassObject ex) { + ex = f.getARaisedType() and + strictcount(f.getARaisedType()) = 1 and + not exists(f.getFunction().getANormalExit()) and + /* raising StopIteration is equivalent to a return in a generator */ + not ex = theStopIterationType() +} + +from FunctionObject f, ClassObject cls, string message +where f.getFunction().isSpecialMethod() and +not is_abstract(f) and +always_raises(f, cls) and +( + no_need_to_raise(f.getName(), message) and not cls.getName() = "NotImplementedError" + or + not correct_raise(f.getName(), cls) and not cls.getName() = "NotImplementedError" + and + exists(ClassObject preferred | + preferred_raise(f.getName(), preferred) | + message = "raise " + preferred.getName() + " instead" + ) +) +select f, "Function always raises $@; " + message, cls, cls.toString() diff --git a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod2.py b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod2.py new file mode 100644 index 000000000000..405400bfe614 --- /dev/null +++ b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod2.py @@ -0,0 +1,15 @@ + +#Abstract base class, but don't declare it. +class ImplicitAbstractClass(object): + + def __add__(self, other): + raise NotImplementedError() + +#Make abstractness explicit. +class ExplicitAbstractClass: + __metaclass__ = ABCMeta + + @abstractmethod + def __add__(self, other): + raise NotImplementedError() + diff --git a/python/ql/src/Functions/IncorrectRaiseInSpecialMethod3.py b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod3.py new file mode 100644 index 000000000000..048d5043b4dc --- /dev/null +++ b/python/ql/src/Functions/IncorrectRaiseInSpecialMethod3.py @@ -0,0 +1,27 @@ + +#Incorrect file-backed table +class FileBackedTable(object): + + def __getitem__(self, key): + if key not in self.index: + raise IOError("Key '%s' not in table" % key) + else: + #May raise an IOError + return self.backing.get_row(key) + +#Correct by transforming exception +class ObjectLikeFileBackedTable(object): + + def get_from_key(self, key): + if key not in self.index: + raise IOError("Key '%s' not in table" % key) + else: + #May raise an IOError + return self.backing.get_row(key) + + def __getitem__(self, key): + try: + return self.get_from_key(key) + except IOError: + raise KeyError(key) + diff --git a/python/ql/src/Functions/IncorrectlyOverriddenMethod.qhelp b/python/ql/src/Functions/IncorrectlyOverriddenMethod.qhelp new file mode 100644 index 000000000000..89869efda71b --- /dev/null +++ b/python/ql/src/Functions/IncorrectlyOverriddenMethod.qhelp @@ -0,0 +1,41 @@ + + + + + +

    There is a call to the overridden method, and potentially the overriding method, +with arguments that are not legal for the overriding method. +This will cause an error if the overriding method is called and is a +violation of the Liskov substitution principle. +

    + +
    + + +

    Ensure that the overriding method accepts all the parameters that are legal for the +overridden method.

    + +
    + +

    In this example there is a mismatch between the legal parameters for the base +class method (self, source, filename, symbol) and the extension method +(self, source). The extension method can be used to override the base +method as long as values are not specified for the filename and (optional) +symbol parameters. If the extension method was passed the additional +parameters accepted by the base method then an error would occur.

    + + + +

    The extension method should be updated to support the filename and +symbol parameters supported by the overridden method.

    + +
    + + +
  • Wikipedia: Liskov Substitution Principle, Method overriding.
  • + + +
    +
    diff --git a/python/ql/src/Functions/IncorrectlyOverriddenMethod.ql b/python/ql/src/Functions/IncorrectlyOverriddenMethod.ql new file mode 100644 index 000000000000..e5d3947a1a78 --- /dev/null +++ b/python/ql/src/Functions/IncorrectlyOverriddenMethod.ql @@ -0,0 +1,27 @@ +/** + * @name Mismatch between signature and use of an overriding method + * @description Method has a different signature from the overridden method and, if it were called, would be likely to cause an error. + * @kind problem + * @tags maintainability + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/inheritance/incorrect-overriding-signature + */ + +import python +import Expressions.CallArgs + +from Call call, FunctionObject func, FunctionObject overridden, string problem +where +func.overrides(overridden) and ( + wrong_args(call, func, _, problem) and correct_args_if_called_as_method(call, overridden) + or + exists(string name | + illegally_named_parameter(call, func, name) and problem = "an argument named '" + name + "'" and + overridden.getFunction().getAnArg().(Name).getId() = name + ) +) + +select func, "Overriding method signature does not match $@, where it is passed " + problem + ". Overridden method $@ is correctly specified.", +call, "here", overridden, overridden.descriptiveString() diff --git a/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.qhelp b/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.qhelp new file mode 100644 index 000000000000..6e2ef60596f6 --- /dev/null +++ b/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.qhelp @@ -0,0 +1,39 @@ + + + + + +

    There is a call to the overriding method, and potentially the overridden method, +with arguments that are not legal for the overridden method. +This will cause an error if the overridden method is called and is a +violation of the Liskov substitution principle. +

    +
    + + +

    Ensure that the overridden method accepts all the parameters that are legal for +overriding method(s).

    + +
    + +

    In this example there is a mismatch between the legal parameters for the base +class method (self, source, filename) and the extension method +(self, source). Since there is a call that uses the signature of the extension method +then it can be inferred that the base signature is erroneous and should be updated to +match that of the extension method. +

    + + + +

    The base method should be updated to either remove the filename parameters, or add a default value for it.

    + +
    + + +
  • Wikipedia: Liskov Substitution Principle, Method overriding.
  • + + +
    +
    diff --git a/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.ql b/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.ql new file mode 100644 index 000000000000..3af03a236027 --- /dev/null +++ b/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.ql @@ -0,0 +1,35 @@ +/** + * @name Mismatch between signature and use of an overridden method + * @description Method has a signature that differs from both the signature of its overriding methods and + * the arguments with which it is called, and if it were called, would be likely to cause an error. + * @kind problem + * @tags maintainability + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/inheritance/incorrect-overridden-signature + */ + +import python +import Expressions.CallArgs + +from Call call, FunctionObject func, FunctionObject overriding, string problem +where +not func.getName() = "__init__" and +overriding.overrides(func) and +call = overriding.getAMethodCall().getNode() and +correct_args_if_called_as_method(call, overriding) and +( + arg_count(call)+1 < func.minParameters() and problem = "too few arguments" + or + arg_count(call) >= func.maxParameters() and problem = "too many arguments" + or + exists(string name | call.getAKeyword().getArg() = name and + overriding.getFunction().getAnArg().(Name).getId() = name and + not func.getFunction().getAnArg().(Name).getId() = name and + problem = "an argument named '" + name + "'" + ) +) + +select func, "Overridden method signature does not match $@, where it is passed " + problem + ". Overriding method $@ matches the call.", +call, "call", overriding, overriding.descriptiveString() diff --git a/python/ql/src/Functions/InitIsGenerator.py b/python/ql/src/Functions/InitIsGenerator.py new file mode 100644 index 000000000000..c64cc346203f --- /dev/null +++ b/python/ql/src/Functions/InitIsGenerator.py @@ -0,0 +1,3 @@ +class InitIsGenerator(object): + def __init__(self, i): + yield i \ No newline at end of file diff --git a/python/ql/src/Functions/InitIsGenerator.qhelp b/python/ql/src/Functions/InitIsGenerator.qhelp new file mode 100644 index 000000000000..113e444d1f39 --- /dev/null +++ b/python/ql/src/Functions/InitIsGenerator.qhelp @@ -0,0 +1,28 @@ + + + +

    The __init__ method of a class is used to initialize new objects, +not create them. As such, it should not return any value. +By including a yield expression in the method turns it into a generator method. +On calling it will return a generator resulting in a runtime error.

    + +
    + +

    The presence of a yield expression in an __init__ method +suggests a logical error, so it is not possible to suggest a general fix.

    + +
    + +

    In this example the __init__ method contains a yield expression. This is +not logical in the context of an initializer.

    + + +
    + + +
  • Python: The __init__ method.
  • + +
    +
    diff --git a/python/ql/src/Functions/InitIsGenerator.ql b/python/ql/src/Functions/InitIsGenerator.ql new file mode 100644 index 000000000000..5ad61ae82555 --- /dev/null +++ b/python/ql/src/Functions/InitIsGenerator.ql @@ -0,0 +1,18 @@ +/** + * @name __init__ method is a generator + * @description __init__ method is a generator. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/init-method-is-generator + */ + +import python + +from Function f +where f.isInitMethod() and +(exists(Yield y | y.getScope() = f) or exists(YieldFrom y| y.getScope() = f)) +select f, "__init__ method is a generator." diff --git a/python/ql/src/Functions/IterReturnsNonIterator.py b/python/ql/src/Functions/IterReturnsNonIterator.py new file mode 100644 index 000000000000..91f2ab699de0 --- /dev/null +++ b/python/ql/src/Functions/IterReturnsNonIterator.py @@ -0,0 +1,18 @@ +class MyRange(object): + def __init__(self, low, high): + self.current = low + self.high = high + + def __iter__(self): + return self + +#Fixed version +class MyRange(object): + def __init__(self, low, high): + self.current = low + self.high = high + + def __iter__(self): + while self.current < self.high: + yield self.current + self.current += 1 diff --git a/python/ql/src/Functions/IterReturnsNonIterator.qhelp b/python/ql/src/Functions/IterReturnsNonIterator.qhelp new file mode 100644 index 000000000000..ebb043b5d0f7 --- /dev/null +++ b/python/ql/src/Functions/IterReturnsNonIterator.qhelp @@ -0,0 +1,37 @@ + + + +

    The __iter__ method of a class should return an iterator. + +Iteration in Python relies on this behavior and attempting to iterate over an +instance of a class with an incorrect __iter__ method will raise a TypeError. +

    + + +
    + +

    Make the __iter__ return a new iterator, either as an instance of +a separate class or as a generator.

    + +
    + +

    In this example the MyRange class's __iter__ method does not +return an iterator. This will cause the program to fail when anyone attempts +to use the iterator in a for loop or in statement. +

    + +

    The fixed version implements the __iter__ method as a generator function.

    + + + +
    + + +
  • Python Language Reference: object.__iter__.
  • +
  • Python Standard Library: Iterator Types.
  • + + +
    +
    diff --git a/python/ql/src/Functions/IterReturnsNonIterator.ql b/python/ql/src/Functions/IterReturnsNonIterator.ql new file mode 100644 index 000000000000..7c727af8d4e5 --- /dev/null +++ b/python/ql/src/Functions/IterReturnsNonIterator.ql @@ -0,0 +1,32 @@ +/** + * @name __iter__ method returns a non-iterator + * @description The '__iter__' method returns a non-iterator which, if used in a 'for' loop, would raise a 'TypeError'. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/iter-returns-non-iterator + */ + +import python + +FunctionObject iter_method(ClassObject t) { + result = t.lookupAttribute("__iter__") +} + +cached ClassObject return_type(FunctionObject f) { + exists(ControlFlowNode n, Return ret | + ret.getScope() = f.getFunction() and ret.getValue() = n.getNode() and + n.refersTo(_, result, _) + ) +} + +from ClassObject t, FunctionObject iter +where exists(ClassObject ret_t | iter = iter_method(t) and + ret_t = return_type(iter) and + not ret_t.isIterator() + ) + +select iter, "The '__iter__' method of iterable class $@ does not return an iterator.", t, t.getName() \ No newline at end of file diff --git a/python/ql/src/Functions/IterReturnsNonSelf.py b/python/ql/src/Functions/IterReturnsNonSelf.py new file mode 100644 index 000000000000..6251b87aba7b --- /dev/null +++ b/python/ql/src/Functions/IterReturnsNonSelf.py @@ -0,0 +1,13 @@ +class MyRange(object): + def __init__(self, low, high): + self.current = low + self.high = high + + def __iter__(self): + return self.current + + def next(self): + if self.current > self.high: + raise StopIteration + self.current += 1 + return self.current - 1 \ No newline at end of file diff --git a/python/ql/src/Functions/IterReturnsNonSelf.qhelp b/python/ql/src/Functions/IterReturnsNonSelf.qhelp new file mode 100644 index 000000000000..f614d912ff0a --- /dev/null +++ b/python/ql/src/Functions/IterReturnsNonSelf.qhelp @@ -0,0 +1,37 @@ + + + +

    The __iter__ method of an iterator should return self. +This is important so that iterators can be used as sequences in any context +that expect a sequence. To do so requires that __iter__ is +idempotent on iterators.

    + +

    +Note that sequences and mapping should return a new iterator, it is just the returned +iterator that must obey this constraint. +

    + +
    + +

    Make the __iter__ return self unless the class should not be an iterator, +in which case rename the next (Python 2) or __next__ (Python 3) +to something else.

    + +
    + +

    In this example the Counter class's __iter__ method does not +return self (or even an iterator). This will cause the program to fail when anyone attempts +to use the iterator in a for loop or in statement.

    + + +
    + + +
  • Python Language Reference: object.__iter__.
  • +
  • Python Standard Library: Iterators.
  • + + +
    +
    diff --git a/python/ql/src/Functions/IterReturnsNonSelf.ql b/python/ql/src/Functions/IterReturnsNonSelf.ql new file mode 100644 index 000000000000..0899cf798a12 --- /dev/null +++ b/python/ql/src/Functions/IterReturnsNonSelf.ql @@ -0,0 +1,33 @@ +/** + * @name Iterator does not return self from __iter__ method + * @description Iterator does not return self from __iter__ method, violating the iterator protocol. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/iter-returns-non-self + */ + +import python + +Function iter_method(ClassObject t) { + result = ((FunctionObject)t.lookupAttribute("__iter__")).getFunction() +} + +predicate is_self(Name value, Function f) { + value.getVariable() = ((Name)f.getArg(0)).getVariable() +} + +predicate returns_non_self(Function f) { + exists(f.getFallthroughNode()) + or + exists(Return r | r.getScope() = f and not is_self(r.getValue(), f)) + or + exists(Return r | r.getScope() = f and not exists(r.getValue())) +} + +from ClassObject t, Function iter +where t.isIterator() and iter = iter_method(t) and returns_non_self(iter) +select t, "Class " + t.getName() + " is an iterator but its $@ method does not return 'self'.", iter, iter.getName() \ No newline at end of file diff --git a/python/ql/src/Functions/ModificationOfParameterWithDefault.py b/python/ql/src/Functions/ModificationOfParameterWithDefault.py new file mode 100644 index 000000000000..2ddee367acc0 --- /dev/null +++ b/python/ql/src/Functions/ModificationOfParameterWithDefault.py @@ -0,0 +1,7 @@ + + def __init__(self, name, choices=[], default=[], shortDesc=None, + longDesc=None, hints=None, allowNone=1): # 'default' parameter assigned a value + self.choices = choices + if choices and not default: + default.append(choices[0][1]) # value of 'default' parameter modified + Argument.__init__(self, name, default, shortDesc, longDesc, hints, allowNone=allowNone) \ No newline at end of file diff --git a/python/ql/src/Functions/ModificationOfParameterWithDefault.qhelp b/python/ql/src/Functions/ModificationOfParameterWithDefault.qhelp new file mode 100644 index 000000000000..ff225c689928 --- /dev/null +++ b/python/ql/src/Functions/ModificationOfParameterWithDefault.qhelp @@ -0,0 +1,44 @@ + + + + + +

    The default value of a parameter is computed once when the function is +created, not for every invocation. The "pre-computed" value is then used for every +subsequent call to the function. Consequently, if you modify the default +value for a parameter this "modified" default value is used for the parameter +in future calls to the function. This means that the function may not behave as +expected in future calls and also makes the function more difficult to understand. +

    + +
    + +

    If a parameter has a default value, do not modify the default value. When +you use a mutable object as a default value, you should use a placeholder value +instead of modifying the default value. This is a particular problem when you +work with lists and dictionaries but there are standard methods of avoiding +modifying the default parameter (see References).

    + +
    + +

    In the following example, the default parameter is set with a default +value of an empty list. Other commands in the function then append values to the +list. The next time the function is called, the list will contain values, which +may not have been intended.

    + + +

    The recommended workaround is use a placeholder value. That is, define the +function with a default of default=None, check if the parameter is +None and then set the parameter to a list.

    + +
    + + +
  • Effbot: Default Parameter Values in Python.
  • +
  • Python Language Reference: Function definitions.
  • + + +
    +
    diff --git a/python/ql/src/Functions/ModificationOfParameterWithDefault.ql b/python/ql/src/Functions/ModificationOfParameterWithDefault.ql new file mode 100644 index 000000000000..03e76477dea2 --- /dev/null +++ b/python/ql/src/Functions/ModificationOfParameterWithDefault.ql @@ -0,0 +1,61 @@ +/** + * @name Modification of parameter with default + * @description Modifying the default value of a parameter can lead to unexpected + * results. + * @kind problem + * @tags reliability + * maintainability + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/modification-of-default-value + */ + +import python + +predicate safe_method(string name) { + name = "count" or name = "index" or name = "copy" or name = "get" or name = "has_key" or + name = "items" or name = "keys" or name = "values" or name = "iteritems" or name = "iterkeys" or name = "itervalues" +} + +predicate maybe_parameter(SsaVariable var, Function f, Parameter p) { + p = var.getAnUltimateDefinition().getDefinition().getNode() and + f.getAnArg() = p +} + +Name use_of_parameter(Parameter p) { + exists(SsaVariable var | + p = var.getAnUltimateDefinition().getDefinition().getNode() and + var.getAUse().getNode() = result + ) +} + +predicate modifying_call(Call c, Parameter p) { + exists(Attribute a | + c.getFunc() = a | + a.getObject() = use_of_parameter(p) and + not safe_method(a.getName()) + ) +} + +predicate is_modification(AstNode a, Parameter p) { + modifying_call(a, p) + or + a.(AugAssign).getTarget() = use_of_parameter(p) +} + +predicate has_mutable_default(Parameter p) { + exists(SsaVariable v, FunctionExpr f | maybe_parameter(v, f.getInnerScope(), p) and + exists(int i, int def_cnt, int arg_cnt | + def_cnt = count(f.getArgs().getADefault()) and + arg_cnt = count(f.getInnerScope().getAnArg()) and + i in [1 .. arg_cnt] and + (f.getArgs().getDefault(def_cnt - i) instanceof Dict or f.getArgs().getDefault(def_cnt - i) instanceof List) and + f.getInnerScope().getArgName(arg_cnt - i) = v.getId() + ) + ) +} + +from AstNode a, Parameter p +where has_mutable_default(p) and is_modification(a, p) +select a, "Modification of parameter $@, which has mutable default value.", p, p.asName().getId() diff --git a/python/ql/src/Functions/NonCls.py b/python/ql/src/Functions/NonCls.py new file mode 100644 index 000000000000..f4959d89ebd3 --- /dev/null +++ b/python/ql/src/Functions/NonCls.py @@ -0,0 +1,4 @@ +class Entry(object): + @classmethod + def make(klass): + return Entry() diff --git a/python/ql/src/Functions/NonCls.qhelp b/python/ql/src/Functions/NonCls.qhelp new file mode 100644 index 000000000000..0e658e7a6b9e --- /dev/null +++ b/python/ql/src/Functions/NonCls.qhelp @@ -0,0 +1,35 @@ + + + + + +

    The first argument of a class method, a new method or any metaclass method +should be called cls. This makes the purpose of the argument clear to other developers. +

    + +
    + + +

    Change the name of the first argument to cls as recommended by the style guidelines +in PEP 8.

    + +
    + +

    In the example, the first parameter to make() is klass which should be changed to cls +for ease of comprehension. +

    + + + + +
    + + +
  • Python PEP 8: Function and method arguments.
  • +
  • Python Tutorial: Classes.
  • + + +
    +
    diff --git a/python/ql/src/Functions/NonCls.ql b/python/ql/src/Functions/NonCls.ql new file mode 100644 index 000000000000..015263df19f6 --- /dev/null +++ b/python/ql/src/Functions/NonCls.ql @@ -0,0 +1,47 @@ +/** + * @name First parameter of a class method is not named 'cls' + * @description Using an alternative name for the first argument of a class method makes code more + * difficult to read; PEP8 states that the first argument to class methods should be 'cls'. + * @kind problem + * @tags maintainability + * readability + * convention + * @problem.severity recommendation + * @sub-severity high + * @precision high + * @id py/not-named-cls + */ + +import python + +predicate first_arg_cls(Function f) { + exists(string argname | argname = f.getArgName(0) | + argname = "cls" or + /* Not PEP8, but relatively common */ + argname = "mcls" + ) +} + +predicate is_type_method(Function f) { + exists(ClassObject c | c.getPyClass() = f.getScope() and c.getASuperType() = theTypeType()) +} + +predicate classmethod_decorators_only(Function f) { + forall(Expr decorator | + decorator = f.getADecorator() | + ((Name) decorator).getId() = "classmethod") +} + +from Function f, string message +where (f.getADecorator().(Name).getId() = "classmethod" or is_type_method(f)) and +not first_arg_cls(f) and classmethod_decorators_only(f) and +not f.getName() = "__new__" and +( + if exists(f.getArgName(0)) then + message = "Class methods or methods of a type deriving from type should have 'cls', rather than '" + + f.getArgName(0) + "', as their first argument." + else + message = "Class methods or methods of a type deriving from type should have 'cls' as their first argument." +) + +select f, message diff --git a/python/ql/src/Functions/NonSelf.py b/python/ql/src/Functions/NonSelf.py new file mode 100644 index 000000000000..845172717e41 --- /dev/null +++ b/python/ql/src/Functions/NonSelf.py @@ -0,0 +1,9 @@ +class Point: + def __init__(val, x, y): # first argument is mis-named 'val' + val._x = x + val._y = y + +class Point2: + def __init__(self, x, y): # first argument is correctly named 'self' + self._x = x + self._y = y \ No newline at end of file diff --git a/python/ql/src/Functions/NonSelf.qhelp b/python/ql/src/Functions/NonSelf.qhelp new file mode 100644 index 000000000000..c4cef70e731b --- /dev/null +++ b/python/ql/src/Functions/NonSelf.qhelp @@ -0,0 +1,38 @@ + + + + + +

    Normal methods should have at least one parameter and the first parameter should be called self. +This makes the purpose of the parameter clear to other developers. +

    +
    + + +

    If there is at least one parameter, then change the name of the first parameter to self as recommended by the style guidelines +in PEP 8.

    +

    If there are no parameters, then it cannot be a normal method. It may need to be marked as a staticmethod +or it could be moved out of the class as a normal function. +

    +
    + + +

    The following methods can both be used to assign values to variables in a point +object. The second method makes the association clearer because the self parameter is +used.

    + + + +
    + + +
  • Python PEP 8: Function and +method arguments.
  • +
  • Python Tutorial: Classes.
  • + + + +
    +
    diff --git a/python/ql/src/Functions/NonSelf.ql b/python/ql/src/Functions/NonSelf.ql new file mode 100644 index 000000000000..37b7ee0ef064 --- /dev/null +++ b/python/ql/src/Functions/NonSelf.ql @@ -0,0 +1,54 @@ +/** + * @name First argument of a method is not named 'self' + * @description Using an alternative name for the first argument of an instance method makes + * code more difficult to read; PEP8 states that the first argument to instance + * methods should be 'self'. + * @kind problem + * @tags maintainability + * readability + * convention + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/not-named-self + */ + +import python +import semmle.python.libraries.Zope + +predicate first_arg_self(Function f) { + f.getArgName(0) = "self" +} + +predicate is_type_method(FunctionObject f) { + exists(ClassObject c | c.lookupAttribute(_) = f and c.getASuperType() = theTypeType()) +} + +predicate used_in_defining_scope(FunctionObject f) { + exists(Call c | + c.getScope() = f.getFunction().getScope() and + c.getFunc().refersTo(f) + ) +} + +from Function f, PyFunctionObject func, string message +where +exists(ClassObject cls, string name | + cls.declaredAttribute(name) = func and cls.isNewStyle() and + not name = "__new__" and + not name = "__metaclass__" and + /* declared in scope */ + f.getScope() = cls.getPyClass() +) and +not first_arg_self(f) and not is_type_method(func) and +func.getFunction() = f and not f.getName() = "lambda" and +not used_in_defining_scope(func) and +( + if exists(f.getArgName(0)) then + message = "Normal methods should have 'self', rather than '" + f.getArgName(0) + "', as their first parameter." + else + message = "Normal methods should have at least one parameter (the first of which should be 'self')." and not f.hasVarArg() +) and +not func instanceof ZopeInterfaceMethod + +select f, message diff --git a/python/ql/src/Functions/OverlyComplexDelMethod.py b/python/ql/src/Functions/OverlyComplexDelMethod.py new file mode 100644 index 000000000000..61e5e28dd6f4 --- /dev/null +++ b/python/ql/src/Functions/OverlyComplexDelMethod.py @@ -0,0 +1,24 @@ + +#Relies on __del__ being called by the garbage collector. +class CachedPreferencesFile + + ... + + def __del__(self): + for key, value in self.preferences.items(): + self.write_pair(key, value) + self.backing.close() + + +#Better version +class CachedPreferencesFile + + ... + + def close(self): + for key, value in self.preferences.items(): + self.write_pair(key, value) + self.backing.close() + + def __del__(self): + self.close() diff --git a/python/ql/src/Functions/OverlyComplexDelMethod.qhelp b/python/ql/src/Functions/OverlyComplexDelMethod.qhelp new file mode 100644 index 000000000000..71410d63a788 --- /dev/null +++ b/python/ql/src/Functions/OverlyComplexDelMethod.qhelp @@ -0,0 +1,42 @@ + + + + +

    The __del__ method exists to release any resources held by an object when that object is deleted. +The __del__ is called only by the garbage collector which may call it after an indefinite delay or +never. +

    + +

    +Consequently, __del__ method should not be relied on to release resources, such as file descriptors. +Rather, these resources should be released explicitly. +

    + +

    The existence of a complex __del__ method suggests that this is the main or only way to release resources +associated with the object.

    + +
    + + +

    In order to ensure correct cleanup of the object add an explicit close(), or similar, +method. Possibly make the object a context manager.

    + +

    The __del__ method should just call close()

    + + +
    + +

    The first example below shows a class which relies on __del__ to release resources. +The second example shows an improved version of the class where __del__ simply calls close.

    + + + +
    + + +
  • Python Standard Library: Context manager.
  • + +
    +
    diff --git a/python/ql/src/Functions/OverlyComplexDelMethod.ql b/python/ql/src/Functions/OverlyComplexDelMethod.ql new file mode 100644 index 000000000000..fff4b3fad0b9 --- /dev/null +++ b/python/ql/src/Functions/OverlyComplexDelMethod.ql @@ -0,0 +1,21 @@ +/** + * @name Overly complex __del__ method + * @description __del__ methods may be called at arbitrary times, perhaps never called at all, and should be simple. + * @kind problem + * @tags efficiency + * maintainability + * complexity + * statistical + * non-attributable + * @problem.severity recommendation + * @sub-severity low + * @precision high + * @id py/overly-complex-delete + */ + +import python + +from FunctionObject method +where exists(ClassObject c | c.declaredAttribute("__del__") = method and +method.getFunction().getMetrics().getCyclomaticComplexity() > 3) +select method, "Overly complex '__del__' method." diff --git a/python/ql/src/Functions/ReturnConsistentTupleSizes.py b/python/ql/src/Functions/ReturnConsistentTupleSizes.py new file mode 100644 index 000000000000..b5ced0685db8 --- /dev/null +++ b/python/ql/src/Functions/ReturnConsistentTupleSizes.py @@ -0,0 +1,15 @@ +def sum_length_product1(l): + if l == []: + return 0, 0 # this tuple has the wrong length + else: + val = l[0] + restsum, restlength, restproduct = sum_length_product1(l[1:]) + return restsum + val, restlength + 1, restproduct * val + +def sum_length_product2(l): + if l == []: + return 0, 0, 1 # this tuple has the correct length + else: + val = l[0] + restsum, restlength, restproduct = sum_length_product2(l[1:]) + return restsum + val, restlength + 1, restproduct * val diff --git a/python/ql/src/Functions/ReturnConsistentTupleSizes.qhelp b/python/ql/src/Functions/ReturnConsistentTupleSizes.qhelp new file mode 100644 index 000000000000..2ebcdc5721de --- /dev/null +++ b/python/ql/src/Functions/ReturnConsistentTupleSizes.qhelp @@ -0,0 +1,39 @@ + + + + + +

    + A common pattern for functions returning multiple arguments is to return a + single tuple containing said arguments. If the function has multiple return + points, care must be taken to ensure that the tuples returned have the same + length. +

    +
    + + +

    Ensure that the function returns tuples of similar lengths.

    + +
    + +

    + In this example, the sum_length_product1 function + simultaneously calculates the sum, length, and product of the values in the + given list. For empty lists, however, the returned tuple only contains the + sum and length of the list. In sum_length_product2 this error + has been corrected. +

    + + + +
    + + +
  • Python Language Reference: Function definitions. +
  • + + +
    +
    diff --git a/python/ql/src/Functions/ReturnConsistentTupleSizes.ql b/python/ql/src/Functions/ReturnConsistentTupleSizes.ql new file mode 100644 index 000000000000..010bbd07ccbf --- /dev/null +++ b/python/ql/src/Functions/ReturnConsistentTupleSizes.ql @@ -0,0 +1,29 @@ +/** + * @name Returning tuples with varying lengths + * @description A function that potentially returns tuples of different lengths may indicate a problem. + * @kind problem + * @tags reliability + * maintainability + * @problem.severity recommendation + * @sub-severity high + * @precision high + * @id py/mixed-tuple-returns + */ + +import python + +predicate returns_tuple_of_size(Function func, int size, AstNode origin) { + exists(Return return, TupleObject val | + return.getScope() = func and + return.getValue().refersTo(val, origin) | + size = val.getLength() + ) +} + + +from Function func, int s1, int s2, AstNode t1, AstNode t2 +where + returns_tuple_of_size(func, s1, t1) and + returns_tuple_of_size(func, s2, t2) and + s1 < s2 +select func, func.getQualifiedName() + " returns $@ and $@.", t1, "tuple of size " + s1, t2, "tuple of size " + s2 diff --git a/python/ql/src/Functions/ReturnValueIgnored.py b/python/ql/src/Functions/ReturnValueIgnored.py new file mode 100644 index 000000000000..848517964c13 --- /dev/null +++ b/python/ql/src/Functions/ReturnValueIgnored.py @@ -0,0 +1,21 @@ + +from third_party import get_resource + +def ignore_error(name): + rsc = get_resource(name) + rsc.initialize() + try: + use_resource(rsc) + finally: + rsc.close() + +#Fixed +def do_not_ignore_error(name): + rsc = get_resource(name) + success = rsc.initialize() + if not success: + raise Error("Could not initialize resource") + try: + use_resource(rsc) + finally: + rsc.close() diff --git a/python/ql/src/Functions/ReturnValueIgnored.qhelp b/python/ql/src/Functions/ReturnValueIgnored.qhelp new file mode 100644 index 000000000000..7081d247112d --- /dev/null +++ b/python/ql/src/Functions/ReturnValueIgnored.qhelp @@ -0,0 +1,45 @@ + + + + +

    When a function returns a non-trivial value, that value should not be ignored. Doing so may result in errors being ignored or +information being thrown away.

    + +

    A return value is considered to be trivial if it is None or it is a parameter (parameters, usually self are often +returned to assist with method chaining, but can be ignored). +A return value is also assumed to be trivial if it is ignored for 75% or more of calls. +

    + +
    + + +

    Act upon all non-trivial return values, either propagating each value or recording it. +If a return value should be ignored, then ensure that it is ignored consistently. +

    + +

    +If you have access to the source code of the called function, then consider modifying it so that it does not return pointless values. +

    + + +
    + + +

    +In the ignore_error function the error condition is ignored. +Ideally the Resource.initialize() function would raise an exception if it failed, but as it does not, the caller must deal with the error. +The do_not_ignore_error function checks the error condition and raises an exception if Resource.initialize() fails. +

    + + + +
    + + +
  • Python Language Reference: Function definitions. +
  • + +
    +
    diff --git a/python/ql/src/Functions/ReturnValueIgnored.ql b/python/ql/src/Functions/ReturnValueIgnored.ql new file mode 100644 index 000000000000..19896533a7c1 --- /dev/null +++ b/python/ql/src/Functions/ReturnValueIgnored.ql @@ -0,0 +1,72 @@ +/** + * @name Ignored return value + * @description Ignoring return values may result in discarding errors or loss of information. + * @kind problem + * @tags reliability + * readability + * convention + * statistical + * non-attributable + * external/cwe/cwe-252 + * @problem.severity recommendation + * @sub-severity high + * @precision medium + * @id py/ignored-return-value + */ + +import python + +predicate meaningful_return_value(Expr val) { + val instanceof Name + or + val instanceof BooleanLiteral + or + exists(FunctionObject callee | val = callee.getACall().getNode() and returns_meaningful_value(callee)) + or + not exists(FunctionObject callee | val = callee.getACall().getNode()) and not val instanceof Name +} + +/* Value is used before returning, and thus its value is not lost if ignored */ +predicate used_value(Expr val) { + exists(LocalVariable var, Expr other | var.getAnAccess() = val and other = var.getAnAccess() and not other = val) +} + +predicate returns_meaningful_value(FunctionObject f) { + not exists(f.getFunction().getFallthroughNode()) + and + ( + exists(Return ret, Expr val | ret.getScope() = f.getFunction() and val = ret.getValue() | + meaningful_return_value(val) and + not used_value(val) + ) + or + /* Is f a builtin function that returns something other than None? + * Ignore __import__ as it is often called purely for side effects */ + f.isC() and f.getAnInferredReturnType() != theNoneType() and not f.getName() = "__import__" + ) +} + +/* If a call is wrapped tightly in a try-except then we assume it is being executed for the exception. */ +predicate wrapped_in_try_except(ExprStmt call) { + exists(Try t | + exists(t.getAHandler()) and + strictcount(Call c | t.getBody().contains(c)) = 1 and + call = t.getAStmt() + ) +} + +from ExprStmt call, FunctionObject callee, float percentage_used, int total +where call.getValue() = callee.getACall().getNode() and returns_meaningful_value(callee) and +not wrapped_in_try_except(call) and +exists(int unused | + unused = count(ExprStmt e | e.getValue().getAFlowNode() = callee.getACall()) and + total = count(callee.getACall()) | + percentage_used = (100.0*(total-unused)/total).floor() +) and +/* Report an alert if we see at least 5 calls and the return value is used in at least 3/4 of those calls. */ +percentage_used >= 75 and +total >= 5 + +select call, "Call discards return value of function $@. The result is used in " + percentage_used.toString() + "% of calls.", +callee, callee.getName() + diff --git a/python/ql/src/Functions/SignatureIncorrectlyOverriddenMethod.py b/python/ql/src/Functions/SignatureIncorrectlyOverriddenMethod.py new file mode 100644 index 000000000000..731ef72dac0b --- /dev/null +++ b/python/ql/src/Functions/SignatureIncorrectlyOverriddenMethod.py @@ -0,0 +1,14 @@ + +class BaseClass(object): + + def run(self, source, filename, symbol="single"): + ... # Definition + + def load_and_run(self, filename): + source = self.load(filename) + self.run(source, filename) # Matches signature in this class, but not in the derived class. + +class DerivedClass(BaseClass): + + def run(self, source): + ... # Definition diff --git a/python/ql/src/Functions/SignatureOverriddenMethod.py b/python/ql/src/Functions/SignatureOverriddenMethod.py new file mode 100644 index 000000000000..7beddcb9e958 --- /dev/null +++ b/python/ql/src/Functions/SignatureOverriddenMethod.py @@ -0,0 +1,9 @@ + +# Base class method +def runsource(self, source, filename="", symbol="single"): + ... # Definition + + +# Extend base class method +def runsource(self, source): + ... # Definition \ No newline at end of file diff --git a/python/ql/src/Functions/SignatureOverriddenMethod.qhelp b/python/ql/src/Functions/SignatureOverriddenMethod.qhelp new file mode 100644 index 000000000000..b7da2678e3d8 --- /dev/null +++ b/python/ql/src/Functions/SignatureOverriddenMethod.qhelp @@ -0,0 +1,41 @@ + + + + + +

    There are one (or more) legal parameters for an overridden method that are +not legal for an overriding method. This will cause an error when the overriding +method is called with a number of parameters that is legal for the overridden method. +This violates the Liskov substitution principle. +

    + +
    + + +

    Ensure that the overriding method accepts all the parameters that are legal for +overridden method.

    + +
    + +

    In this example there is a mismatch between the legal parameters for the base +class method (self, source, filename, symbol) and the extension method +(self, source). The extension method can be used to override the base +method as long as values are not specified for the filename and +symbol parameters. If the extension method was passed the additional +parameters accepted by the base method then an error would occur.

    + + + +

    The extension method should be updated to support the filename and +symbol parameters supported by the overridden method.

    + +
    + + +
  • Wikipedia: Liskov Substitution Principle, Method overriding.
  • + + +
    +
    diff --git a/python/ql/src/Functions/SignatureOverriddenMethod.ql b/python/ql/src/Functions/SignatureOverriddenMethod.ql new file mode 100644 index 000000000000..47182d8d87d4 --- /dev/null +++ b/python/ql/src/Functions/SignatureOverriddenMethod.ql @@ -0,0 +1,35 @@ +/** + * @name Signature mismatch in overriding method + * @description Overriding a method without ensuring that both methods accept the same + * number and type of parameters has the potential to cause an error when there is a mismatch. + * @kind problem + * @problem.severity warning + * @tags reliability + * correctness + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/inheritance/signature-mismatch + */ + +import python +import Expressions.CallArgs + +from FunctionObject base, PyFunctionObject derived +where + not exists(base.getACall()) and + not exists(FunctionObject a_derived | + a_derived.overrides(base) and + exists(a_derived.getACall()) + ) and + not derived.getFunction().isSpecialMethod() and + derived.getName() != "__init__" and + derived.isNormalMethod() and + not derived.getFunction().isSpecialMethod() and + // call to overrides distributed for efficiency + ( + (derived.overrides(base) and derived.minParameters() > base.maxParameters()) + or + (derived.overrides(base) and derived.maxParameters() < base.minParameters()) + ) +select derived, "Overriding method '" + derived.getName() + "' has signature mismatch with $@.", base, "overridden method" diff --git a/python/ql/src/Functions/SignatureOverridingMethod.py b/python/ql/src/Functions/SignatureOverridingMethod.py new file mode 100644 index 000000000000..f4898ef45b5a --- /dev/null +++ b/python/ql/src/Functions/SignatureOverridingMethod.py @@ -0,0 +1,14 @@ + +class BaseClass(object): + + def run(self, source, filename, symbol="single"): + ... # Definition + + def load_and_run(self, filename): + source = self.load(filename) + self.run(source) # Matches signature in derived class, but not in this class. + +class DerivedClass(BaseClass): + + def run(self, source): + ... # Definition diff --git a/python/ql/src/Functions/SignatureSpecialMethods.py b/python/ql/src/Functions/SignatureSpecialMethods.py new file mode 100644 index 000000000000..343baf55f728 --- /dev/null +++ b/python/ql/src/Functions/SignatureSpecialMethods.py @@ -0,0 +1,18 @@ +#-*- coding: utf-8 -*- + +class Point(object): + + def __init__(self, x, y): + self.x + self.y + + def __add__(self, other): + if not isinstance(other, Point): + return NotImplemented + return Point(self.x + other.x, self.y + other.y) + + def __str__(self, style): #Spurious extra parameter + if style == 'polar': + u"%s @ %s\u00b0" % (abs(self), self.angle()) + else: + return "[%s, %s]" % (self.x, self.y) diff --git a/python/ql/src/Functions/SignatureSpecialMethods.qhelp b/python/ql/src/Functions/SignatureSpecialMethods.qhelp new file mode 100644 index 000000000000..ab25c13f07f1 --- /dev/null +++ b/python/ql/src/Functions/SignatureSpecialMethods.qhelp @@ -0,0 +1,34 @@ + + + +

    Special methods (sometimes also called magic methods) are how user defined classes interact with the Python virtual machine. +For example, for a class to support addition it must implement the __add__ and __radd__ special methods. +When the expression a + b is evaluated the Python virtual machine will call type(a).__add__(a, b) and if that +is not implemented it will call type(b).__radd__(b, a).

    +

    +Since these special methods are always called by the virtual machine with a fixed number of parameters, if the method is implemented with +a different number of parameters it will fail at runtime with a TypeError. +

    + +
    + +

    Ensure that the method has the correct number of parameters

    + +
    + +

    In the example the __str__ method has an extra parameter. This means that if str(p) is called when p +is a Point then it will fail with a TypeError. +

    + + + +
    + + +
  • Python Language Reference: Special Method Names.
  • + + +
    +
    diff --git a/python/ql/src/Functions/SignatureSpecialMethods.ql b/python/ql/src/Functions/SignatureSpecialMethods.ql new file mode 100644 index 000000000000..1301949768a3 --- /dev/null +++ b/python/ql/src/Functions/SignatureSpecialMethods.ql @@ -0,0 +1,200 @@ +/** + * @name Special method has incorrect signature + * @description Special method has incorrect signature + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/special-method-wrong-signature + */ + +import python + + +predicate is_unary_op(string name) { + name = "__del__" or + name = "__repr__" or + name = "__str__" or + name = "__hash__" or + name = "__bool__" or + name = "__nonzero__" or + name = "__unicode__" or + name = "__len__" or + name = "__iter__" or + name = "__reversed__" or + name = "__neg__" or + name = "__pos__" or + name = "__abs__" or + name = "__invert__" or + name = "__complex__" or + name = "__int__" or + name = "__float__" or + name = "__long__" or + name = "__oct__" or + name = "__hex__" or + name = "__index__" or + name = "__enter__" +} + +predicate is_binary_op(string name) { + name = "__lt__" or + name = "__le__" or + name = "__eq__" or + name = "__ne__" or + name = "__gt__" or + name = "__ge__" or + name = "__cmp__" or + name = "__rcmp__" or + name = "__getattr___" or + name = "__getattribute___" or + name = "__delattr__" or + name = "__delete__" or + name = "__instancecheck__" or + name = "__subclasscheck__" or + name = "__getitem__" or + name = "__delitem__" or + name = "__contains__" or + name = "__add__" or + name = "__sub__" or + name = "__mul__" or + name = "__floordiv__" or + name = "__div__" or + name = "__truediv__" or + name = "__mod__" or + name = "__divmod__" or + name = "__lshift__" or + name = "__rshift__" or + name = "__and__" or + name = "__xor__" or + name = "__or__" or + name = "__radd__" or + name = "__rsub__" or + name = "__rmul__" or + name = "__rfloordiv__" or + name = "__rdiv__" or + name = "__rtruediv__" or + name = "__rmod__" or + name = "__rdivmod__" or + name = "__rpow__" or + name = "__rlshift__" or + name = "__rrshift__" or + name = "__rand__" or + name = "__rxor__" or + name = "__ror__" or + name = "__iadd__" or + name = "__isub__" or + name = "__imul__" or + name = "__ifloordiv__" or + name = "__idiv__" or + name = "__itruediv__" or + name = "__imod__" or + name = "__idivmod__" or + name = "__ipow__" or + name = "__ilshift__" or + name = "__irshift__" or + name = "__iand__" or + name = "__ixor__" or + name = "__ior__" or + name = "__coerce__" +} + +predicate is_ternary_op(string name) { + name = "__setattr__" or + name = "__set__" or + name = "__setitem__" or + name = "__getslice__" or + name = "__delslice__" +} + +predicate is_quad_op(string name) { + name = "__setslice__" or name = "__exit__" +} + +int argument_count(PyFunctionObject f, string name, ClassObject cls) { + cls.declaredAttribute(name) = f and + ( + is_unary_op(name) and result = 1 + or + is_binary_op(name) and result = 2 + or + is_ternary_op(name) and result = 3 + or + is_quad_op(name) and result = 4 + ) +} + +predicate incorrect_special_method_defn(PyFunctionObject func, string message, boolean show_counts, string name, ClassObject owner) { + exists(int required | + required = argument_count(func, name, owner) | + /* actual_non_default <= actual */ + if required > func.maxParameters() then + (message = "Too few parameters" and show_counts = true) + else if required < func.minParameters() then + (message = "Too many parameters" and show_counts = true) + else if (func.minParameters() < required and not func.getFunction().hasVarArg()) then + (message = (required -func.minParameters()) + " default values(s) will never be used" and show_counts = false) + else + none() + ) +} + +predicate incorrect_pow(FunctionObject func, string message, boolean show_counts, ClassObject owner) { + owner.declaredAttribute("__pow__") = func and + ( + func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true + or + func.minParameters() > 3 and message = "Too many parameters" and show_counts = true + or + func.minParameters() < 2 and message = (2 - func.minParameters()) + " default value(s) will never be used" and show_counts = false + or + func.minParameters() = 3 and message = "Third parameter to __pow__ should have a default value" and show_counts = false + ) +} + +predicate incorrect_get(FunctionObject func, string message, boolean show_counts, ClassObject owner) { + owner.declaredAttribute("__get__") = func and + ( + func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true + or + func.minParameters() > 3 and message = "Too many parameters" and show_counts = true + or + func.minParameters() < 2 and not func.getFunction().hasVarArg() and + message = (2 - func.minParameters()) + " default value(s) will never be used" and show_counts = false + ) +} + +string should_have_parameters(PyFunctionObject f, string name, ClassObject owner) { + exists(int i | i = argument_count(f, name, owner) | + result = i.toString() + ) + or + owner.declaredAttribute(name) = f and (name = "__get__" or name = "__pow__") and result = "2 or 3" +} + +string has_parameters(PyFunctionObject f) { + exists(int i | i = f.minParameters() | + i = 0 and result = "no parameters" + or + i = 1 and result = "1 parameter" + or + i > 1 and result = i.toString() + " parameters" + ) +} + +from PyFunctionObject f, string message, string sizes, boolean show_counts, string name, ClassObject owner +where + ( + incorrect_special_method_defn(f, message, show_counts, name, owner) + or + incorrect_pow(f, message, show_counts, owner) and name = "__pow__" + or + incorrect_get(f, message, show_counts, owner) and name = "__get__" + ) + and + ( + show_counts = false and sizes = "" or + show_counts = true and sizes = ", which has " + has_parameters(f) + ", but should have " + should_have_parameters(f, name, owner) + ) +select f, message + " for special method " + name + sizes + ", in class $@.", owner, owner.getName() diff --git a/python/ql/src/Functions/UseImplicitNoneReturnValue.py b/python/ql/src/Functions/UseImplicitNoneReturnValue.py new file mode 100644 index 000000000000..fcaafcfde75a --- /dev/null +++ b/python/ql/src/Functions/UseImplicitNoneReturnValue.py @@ -0,0 +1,17 @@ + +import sys + +def my_print(*args): + print (args) + +def main(): + err = my_print(sys.argv) + if err: + sys.exit(err) + + +#FIXED VERSION +def main(): + my_print(sys.argv) + #The rest of the code can be removed as None as always false + diff --git a/python/ql/src/Functions/UseImplicitNoneReturnValue.qhelp b/python/ql/src/Functions/UseImplicitNoneReturnValue.qhelp new file mode 100644 index 000000000000..2637ac21ef3f --- /dev/null +++ b/python/ql/src/Functions/UseImplicitNoneReturnValue.qhelp @@ -0,0 +1,32 @@ + + + +

    All functions in Python return a value. +If a function has no return statements or none of the return statements return a value +then the function will return None. However, this value has no meaning and should be ignored.

    + +

    Using the return value of such a 'procedure' is confusing to the reader as it suggests +that the value is significant. +

    + +
    + +

    Do not use the return value of a procedure; replace x = proc() with proc() +and replace any use of the value with None.

    + +
    + +

    In this example, the my_print function is a procedure as it returns no value of any meaning. +Using the return value is misleading in subsequent code. +

    + + +
    + + +
  • Python Library Reference: None.
  • + +
    +
    diff --git a/python/ql/src/Functions/UseImplicitNoneReturnValue.ql b/python/ql/src/Functions/UseImplicitNoneReturnValue.ql new file mode 100644 index 000000000000..d2c954cb4c9d --- /dev/null +++ b/python/ql/src/Functions/UseImplicitNoneReturnValue.ql @@ -0,0 +1,34 @@ +/** + * @name Use of the return value of a procedure + * @description The return value of a procedure (a function that does not return a value) is used. This is confusing to the reader as the value (None) has no meaning. + * @kind problem + * @tags maintainability + * @problem.severity warning + * @sub-severity low + * @precision high + * @id py/procedure-return-value-used + */ + +import python +import Testing.Mox + +predicate is_used(Call c) { + exists(Expr outer | outer != c and outer.containsInScope(c) | outer instanceof Call or outer instanceof Attribute or outer instanceof Subscript) + or + exists(Stmt s | + c = s.getASubExpression() and + not s instanceof ExprStmt and + /* Ignore if a single return, as def f(): return g() is quite common. Covers implicit return in a lambda. */ + not (s instanceof Return and strictcount(Return r | r.getScope() = s.getScope()) = 1) + ) +} + +from Call c, FunctionObject func +where +/* Call result is used, but callee is a procedure */ +is_used(c) and c.getFunc().refersTo(func) and func.getFunction().isProcedure() and +/* All callees are procedures */ +forall(FunctionObject callee | c.getFunc().refersTo(callee) | callee.getFunction().isProcedure()) and +/* Mox return objects have an `AndReturn` method */ +not useOfMoxInModule(c.getEnclosingModule()) +select c, "The result of '$@' is used even though it is always None.", func, func.getQualifiedName() diff --git a/python/ql/src/Imports/Cyclic.qll b/python/ql/src/Imports/Cyclic.qll new file mode 100644 index 000000000000..b16e3ae147c8 --- /dev/null +++ b/python/ql/src/Imports/Cyclic.qll @@ -0,0 +1,89 @@ +import python + +predicate is_import_time(Stmt s) { + not s.getScope+() instanceof Function +} + +PythonModuleObject module_imported_by(PythonModuleObject m) { + exists(Stmt imp | + result = stmt_imports(imp) and + imp.getEnclosingModule() = m.getModule() and + // Import must reach exit to be part of a cycle + imp.getAnEntryNode().getBasicBlock().reachesExit() + ) +} + +/** Is there a circular import of 'm1' beginning with 'm2'? */ +predicate circular_import(PythonModuleObject m1, PythonModuleObject m2) { + m1 != m2 and + m2 = module_imported_by(m1) and m1 = module_imported_by+(m2) +} + +ModuleObject stmt_imports(ImportingStmt s) { + exists(string name | + result.importedAs(name) and not name = "__main__" | + name = s.getAnImportedModuleName() + ) +} + +predicate import_time_imported_module(PythonModuleObject m1, PythonModuleObject m2, Stmt imp) { + imp.getEnclosingModule() = m1.getModule() and + is_import_time(imp) and + m2 = stmt_imports(imp) +} + +/** Is there a cyclic import of 'm1' beginning with an import 'm2' at 'imp' where all the imports are top-level? */ +predicate import_time_circular_import(PythonModuleObject m1, PythonModuleObject m2, Stmt imp) { + m1 != m2 and + import_time_imported_module(m1, m2, imp) and + import_time_transitive_import(m2, _, m1) +} + +predicate import_time_transitive_import(PythonModuleObject base, Stmt imp, PythonModuleObject last) { + last != base and + ( + import_time_imported_module(base, last, imp) + or + exists(PythonModuleObject mid | + import_time_transitive_import(base, imp, mid) and + import_time_imported_module(mid, last, _) + ) + ) and + // Import must reach exit to be part of a cycle + imp.getAnEntryNode().getBasicBlock().reachesExit() +} + +/** + * Returns import-time usages of module 'm' in module 'enclosing' + */ +predicate import_time_module_use(PythonModuleObject m, PythonModuleObject enclosing, Expr use, string attr) { + exists(Expr mod | + use.getEnclosingModule() = enclosing.getModule() and + not use.getScope+() instanceof Function + and mod.refersTo(m) + | + // either 'M.foo' + use.(Attribute).getObject() = mod and use.(Attribute).getName() = attr + or + // or 'from M import foo' + use.(ImportMember).getModule() = mod and use.(ImportMember).getName() = attr + ) +} + +/** Whether importing module 'first' before importing module 'other' will fail at runtime, due to an + AttributeError at 'use' (in module 'other') caused by 'first.attr' not being defined as its definition can + occur after the import 'other' in 'first'. +*/ +predicate failing_import_due_to_cycle(PythonModuleObject first, PythonModuleObject other, Stmt imp, + ControlFlowNode defn, Expr use, string attr) { + import_time_imported_module(other, first, _) and + import_time_transitive_import(first, imp, other) and + import_time_module_use(first, other, use, attr) and + exists(ImportTimeScope n, SsaVariable v | + defn = v.getDefinition() and + n = first.getModule() and v.getVariable().getScope() = n and v.getId() = attr | + not defn.strictlyDominates(imp.getAnEntryNode()) + ) + and not exists(If i | i.isNameEqMain() and i.contains(use)) +} + diff --git a/python/ql/src/Imports/CyclicImport.qhelp b/python/ql/src/Imports/CyclicImport.qhelp new file mode 100644 index 000000000000..0d84c64418a5 --- /dev/null +++ b/python/ql/src/Imports/CyclicImport.qhelp @@ -0,0 +1,36 @@ + + + +

    A cyclic import is an import which imports another module +and that module imports (possibly indirectly) the module which contains the +import statement.

    + +

    Cyclic imports indicate that two modules are circularly dependent. This means +that the modules cannot be tested independently, and it makes it harder to +understand the architecture of the system. +

    + +
    + + +

    The cycle may be broken by removing any one import. If only one function or +method requires the import, then consider moving that to the other module and +deleting the import. If the two modules are more intimately connected, then move +the inter-dependent parts into a third module and have both the original modules +import that. +

    + + +
    + + + +
  • Python Language Reference: The import statement.
  • +
  • Python: Modules.
  • +
  • Effbot: Import Confusion.
  • + + +
    +
    diff --git a/python/ql/src/Imports/CyclicImport.ql b/python/ql/src/Imports/CyclicImport.ql new file mode 100644 index 000000000000..1e1586c2f932 --- /dev/null +++ b/python/ql/src/Imports/CyclicImport.ql @@ -0,0 +1,27 @@ +/** + * @name Cyclic import + * @description Module forms part of an import cycle, thereby indirectly importing itself. + * @kind problem + * @tags reliability + * maintainability + * modularity + * @problem.severity recommendation + * @sub-severity low + * @precision high + * @id py/cyclic-import + */ + +import python +import Cyclic + +from PythonModuleObject m1, PythonModuleObject m2, Stmt imp +where + imp.getEnclosingModule() = m1.getModule() + and stmt_imports(imp) = m2 + and circular_import(m1, m2) + and m1 != m2 + // this query finds all cyclic imports that are *not* flagged by ModuleLevelCyclicImport + and not failing_import_due_to_cycle(m2, m1, _, _, _, _) + and not exists(If i | i.isNameEqMain() and i.contains(imp)) +select imp, "Import of module $@ begins an import cycle.", m2, m2.getName() + diff --git a/python/ql/src/Imports/DeprecatedModule.qhelp b/python/ql/src/Imports/DeprecatedModule.qhelp new file mode 100644 index 000000000000..53c0d1abbc64 --- /dev/null +++ b/python/ql/src/Imports/DeprecatedModule.qhelp @@ -0,0 +1,23 @@ + + + +

    A module is deprecated when it cannot or will not be maintained indefinitely in the standard library. +Deprecated modules may not receive security fixes or other important updates. +See PEP 4 for a list of all deprecated modules. +

    +
    + + +

    Do not import the deprecated module. Replace uses of it with uses of a better maintained module. +

    + +
    + + +
  • Python PEPs: PEP 4 -- Deprecation of Standard Modules .
  • + + +
    +
    diff --git a/python/ql/src/Imports/DeprecatedModule.ql b/python/ql/src/Imports/DeprecatedModule.ql new file mode 100644 index 000000000000..22f4f962e314 --- /dev/null +++ b/python/ql/src/Imports/DeprecatedModule.ql @@ -0,0 +1,73 @@ +/** + * @name Import of deprecated module + * @description Import of a deprecated module + * @kind problem + * @tags maintainability + * external/cwe/cwe-477 + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/import-deprecated-module + */ + +import python + + +predicate deprecated_module(string name, string instead, int major, int minor) { + name = "posixfile" and instead = "email" and major = 1 and minor = 5 + or + name = "gopherlib" and instead = "no replacement" and major = 2 and minor = 5 + or + name = "rgbimgmodule" and instead = "no replacement" and major = 2 and minor = 5 + or + name = "pre" and instead = "re" and major = 1 and minor = 5 + or + name = "whrandom" and instead = "random" and major = 2 and minor = 1 + or + name = "rfc822" and instead = "email" and major = 2 and minor = 3 + or + name = "mimetools" and instead = "email" and major = 2 and minor = 3 + or + name = "MimeWriter" and instead = "email" and major = 2 and minor = 3 + or + name = "mimify" and instead = "email" and major = 2 and minor = 3 + or + name = "rotor" and instead = "no replacement" and major = 2 and minor = 4 + or + name = "statcache" and instead = "no replacement" and major = 2 and minor = 2 + or + name = "mpz" and instead = "a third party" and major = 2 and minor = 2 + or + name = "xreadlines" and instead = "no replacement" and major = 2 and minor = 3 + or + name = "multifile" and instead = "email" and major = 2 and minor = 5 + or + name = "sets" and instead = "builtins" and major = 2 and minor = 6 + or + name = "buildtools" and instead = "no replacement" and major = 2 and minor = 3 + or + name = "cfmfile" and instead = "no replacement" and major = 2 and minor = 4 + or + name = "macfs" and instead = "no replacement" and major = 2 and minor = 3 + or + name = "md5" and instead = "hashlib" and major = 2 and minor = 5 + or + name = "sha" and instead = "hashlib" and major = 2 and minor = 5 +} + +string deprecation_message(string mod) { + exists(int major, int minor | deprecated_module(mod, _, major, minor) | + result = "The " + mod + " module was deprecated in version " + major.toString() + "." + minor.toString() + ".") +} + +string replacement_message(string mod) { + exists(string instead | deprecated_module(mod, instead, _, _) | + result = " Use " + instead + " module instead." and not instead = "no replacement" + or + result = "" and instead = "no replacement" + ) +} + +from ImportExpr imp, Stmt s, Expr e +where s.getASubExpression() = e and (e = imp or e.contains(imp)) +select s, deprecation_message(imp.getName()) + replacement_message(imp.getName()) diff --git a/python/ql/src/Imports/EncodingError.qhelp b/python/ql/src/Imports/EncodingError.qhelp new file mode 100644 index 000000000000..7142390b6797 --- /dev/null +++ b/python/ql/src/Imports/EncodingError.qhelp @@ -0,0 +1,36 @@ + + + + +

    Encoding errors prevent a module being evaluated and thus imported. +An attempt to import a module with an invalid encoding will fail; a SyntaxError will be raised. +Note that in Python 2, the default encoding is ASCII. +

    + +

    The existence of an encoding error in a module may suggest other problems as well. +Either the module is never imported in practice and could be deleted or a +try statement around the import is mistakenly discarding the SyntaxError. +

    + + +
    + +

    Fixing the encoding error is the obvious fix. +However, it is worth investigating why a module containing an encoding error +was able to persist and address that problem as well. +

    +

    + If a different encoding should be used for the file, specify it explicitly by + putting an encoding specification at the top of the file. For instance, to + specify UTF-8 encoding, add the line # coding=utf-8. +

    + +
    + +
  • Python PEPs: PEP 263 — Defining Python Source Code Encodings.
  • +
  • Python Tutorial: SyntaxErrors.
  • + +
    +
    diff --git a/python/ql/src/Imports/EncodingError.ql b/python/ql/src/Imports/EncodingError.ql new file mode 100644 index 000000000000..f26bf8dad33d --- /dev/null +++ b/python/ql/src/Imports/EncodingError.ql @@ -0,0 +1,16 @@ +/** + * @name Encoding error + * @description Encoding errors cause failures at runtime and prevent analysis of the code. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/encoding-error + */ + +import python + +from EncodingError error +select error, error.getMessage() \ No newline at end of file diff --git a/python/ql/src/Imports/FromImportOfMutableAttribute.qhelp b/python/ql/src/Imports/FromImportOfMutableAttribute.qhelp new file mode 100644 index 000000000000..78b03e1cb7c9 --- /dev/null +++ b/python/ql/src/Imports/FromImportOfMutableAttribute.qhelp @@ -0,0 +1,45 @@ + + + +

    +Explicitly importing an attribute from a module into the current namespace means that the value of that attribute will not be updated if the value in the original module changes. +

    +

    +This can mean that changes in global state are not observed locally, which may lead to inconsistencies and possible errors. +

    + + +
    + +

    Instead of using from module import attr, simply import the module using import module +and replace all uses of attr with module.attr. +

    +
    + + +

    In the first of the two modules shown below, from sys import stdout is used to import the stdout attribute, +rather than using import sys to import the module. Then stdout is used in the main() function. +

    + +

    In the second module, below, a function, redirect_to_file is defined to collect the output from sys.stdout and save it to a file. +However, redirect_to_file will not work correctly when passed the main() function. +This is because the main() function will not see the change to sys.stdout, +as it uses its own version of stdout that was defined when the module was loaded. +

    + +

    +The problem can be fixed by rewriting the first module to import the sys module and write to sys.stdout, as shown below. +

    + +
    + + + +
  • Python Language Reference: The import statement.
  • +
  • Python Tutorial: Modules.
  • + + +
    +
    diff --git a/python/ql/src/Imports/FromImportOfMutableAttribute.ql b/python/ql/src/Imports/FromImportOfMutableAttribute.ql new file mode 100644 index 000000000000..e5e7c96985ea --- /dev/null +++ b/python/ql/src/Imports/FromImportOfMutableAttribute.ql @@ -0,0 +1,31 @@ +/** + * @name Importing value of mutable attribute + * @description Importing the value of a mutable attribute directly means that changes in global state will not be observed locally. + * @kind problem + * @tags reliability + * maintainability + * modularity + * @problem.severity warning + * @sub-severity high + * @precision medium + * @id py/import-of-mutable-attribute + */ +import python +import semmle.python.filters.Tests + +from ImportMember im, ModuleObject m, AttrNode store_attr, string name +where im.getModule().(ImportExpr).getImportedModuleName() = m.getName() and +im.getName() = name and +/* Modification must be in a function, so it can occur during lifetime of the import value */ +store_attr.getScope() instanceof Function and +/* variable resulting from import must have a long lifetime */ +not im.getScope() instanceof Function and +store_attr.isStore() and +store_attr.getObject(name).refersTo(m) and +/* Import not in same module as modification. */ +not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and +/* Modification is not in a test */ +not store_attr.getScope().getScope*() instanceof TestScope + +select im, "Importing the value of '" + name + "' from $@ means that any change made to $@ will be not be observed locally.", +m, "module " + m.getName(), store_attr, m.getName() + "." + store_attr.getName() diff --git a/python/ql/src/Imports/ImportOnTwoLines.py b/python/ql/src/Imports/ImportOnTwoLines.py new file mode 100644 index 000000000000..226b41ccd146 --- /dev/null +++ b/python/ql/src/Imports/ImportOnTwoLines.py @@ -0,0 +1,2 @@ +import xxx +import yyy diff --git a/python/ql/src/Imports/ImportShadowedByLoopVar.qhelp b/python/ql/src/Imports/ImportShadowedByLoopVar.qhelp new file mode 100644 index 000000000000..566d51f07105 --- /dev/null +++ b/python/ql/src/Imports/ImportShadowedByLoopVar.qhelp @@ -0,0 +1,10 @@ + + + + +

    This is defined as an error in PyFlakes.

    + +
    +
    diff --git a/python/ql/src/Imports/ImportShadowedByLoopVar.ql b/python/ql/src/Imports/ImportShadowedByLoopVar.ql new file mode 100644 index 000000000000..29f6536cce78 --- /dev/null +++ b/python/ql/src/Imports/ImportShadowedByLoopVar.ql @@ -0,0 +1,22 @@ +/** + * @name Import shadowed by loop variable + * @description A loop variable shadows an import. + * @kind problem + * @tags maintainability + * @problem.severity recommendation + * @sub-severity low + * @deprecated + * @precision very-high + * @id py/import-shadowed-loop-variable + */ + +import python + +predicate shadowsImport(Variable l) { + exists(Import i, Name shadow | shadow = i.getAName().getAsname() and shadow.getId() = l.getId() and i.getScope() = l.getScope().getScope*()) +} + + +from Variable l, Name defn +where shadowsImport(l) and defn.defines(l) and exists(For for | defn = for.getTarget()) +select defn, "Loop variable '" + l.getId() + "' shadows an import" diff --git a/python/ql/src/Imports/ImportStarUsed.qhelp b/python/ql/src/Imports/ImportStarUsed.qhelp new file mode 100644 index 000000000000..65f92a5f5e04 --- /dev/null +++ b/python/ql/src/Imports/ImportStarUsed.qhelp @@ -0,0 +1,27 @@ + + + + +

    Using from xxx import * makes it difficult to determine what has +been defined by the import statement. This may hide errors and introduce +unexpected dependencies.

    + +
    + + +

    +Use explicit imports. For example from xxx import a, b, c +

    + +
    + + + +
  • Python Language Reference: The import statement.
  • +
  • Python PEP-8: Imports.
  • + + +
    +
    diff --git a/python/ql/src/Imports/ImportStarUsed.ql b/python/ql/src/Imports/ImportStarUsed.ql new file mode 100644 index 000000000000..bc125c05a3b5 --- /dev/null +++ b/python/ql/src/Imports/ImportStarUsed.ql @@ -0,0 +1,17 @@ +/** + * @name 'import *' used + * @description Using import * prevents some analysis + * @kind problem + * @tags maintainability + * @problem.severity recommendation + * @sub-severity low + * @deprecated + * @precision medium + * @id py/import-star-used + */ + +import python + +from ImportStar i +select i, "Using 'from ... import *' pollutes the namespace" + diff --git a/python/ql/src/Imports/ImportTwiceOnALine.py b/python/ql/src/Imports/ImportTwiceOnALine.py new file mode 100644 index 000000000000..4a22939b3336 --- /dev/null +++ b/python/ql/src/Imports/ImportTwiceOnALine.py @@ -0,0 +1 @@ +import xxx, yyy diff --git a/python/ql/src/Imports/ImportandImportFrom.py b/python/ql/src/Imports/ImportandImportFrom.py new file mode 100644 index 000000000000..9c373d6e58b8 --- /dev/null +++ b/python/ql/src/Imports/ImportandImportFrom.py @@ -0,0 +1,2 @@ +import os +from os import walk diff --git a/python/ql/src/Imports/ImportandImportFrom.qhelp b/python/ql/src/Imports/ImportandImportFrom.qhelp new file mode 100644 index 000000000000..58dd1ada0836 --- /dev/null +++ b/python/ql/src/Imports/ImportandImportFrom.qhelp @@ -0,0 +1,27 @@ + + + + + +

    Importing a module twice using the import xxx and +from xxx import yyy is confusing. +

    + +
    + + +

    Remove the from xxx import yyy statement. +Add yyy = xxx.yyy if required.

    + +
    + + + + + + +
  • Python Language Reference: The import statement.
  • +
    +
    diff --git a/python/ql/src/Imports/ImportandImportFrom.ql b/python/ql/src/Imports/ImportandImportFrom.ql new file mode 100644 index 000000000000..6a12e6b938d4 --- /dev/null +++ b/python/ql/src/Imports/ImportandImportFrom.ql @@ -0,0 +1,24 @@ +/** + * @name Module is imported with 'import' and 'import from' + * @description A module is imported with the "import" and "import from" statements + * @kind problem + * @tags maintainability + * @problem.severity recommendation + * @sub-severity low + * @precision very-high + * @id py/import-and-import-from + */ + +import python + +predicate import_and_import_from(Import i1, Import i2, Module m) { + i1.getEnclosingModule() = i2.getEnclosingModule() and + exists (ImportExpr e1, ImportExpr e2, ImportMember im | + e1 = i1.getAName().getValue() and im = i2.getAName().getValue() and e2 = im.getModule() | + e1.getName() = m.getName() and e2.getName() = m.getName() + ) +} + +from Stmt i1, Stmt i2, Module m +where import_and_import_from(i1, i2, m) +select i1, "Module '" + m.getName() + "' is imported with both 'import' and 'import from'" diff --git a/python/ql/src/Imports/Imports.qhelp b/python/ql/src/Imports/Imports.qhelp new file mode 100644 index 000000000000..18df8145f265 --- /dev/null +++ b/python/ql/src/Imports/Imports.qhelp @@ -0,0 +1,29 @@ + + + + + +

    Code is easier to read when each import statement is defined on a separate line. +

    + +
    + +

    Update the code so that each import is defined on a separate line. PEP8 notes that it is +acceptable to define multiple imports from a subprocess in a single statement.

    + +
    + +

    The import statement:

    + +

    should be changed to:

    + +
    + + +
  • Python Language Reference: The import statement.
  • +
  • Python PEP 8: Imports.
  • + +
    +
    diff --git a/python/ql/src/Imports/Imports.ql b/python/ql/src/Imports/Imports.ql new file mode 100644 index 000000000000..7adba83cfe4d --- /dev/null +++ b/python/ql/src/Imports/Imports.ql @@ -0,0 +1,27 @@ +/** + * @name Multiple imports on one line + * @description Defining multiple imports on one line makes code more difficult to read; + * PEP8 states that imports should usually be on separate lines. + * @kind problem + * @tags maintainability + * @problem.severity recommendation + * @sub-severity low + * @deprecated + * @precision medium + * @id py/multiple-imports-on-line + */ + +/* Look for imports of the form: +import modA, modB +(Imports should be one per line according PEP 8) +*/ + +import python + +predicate multiple_import(Import imp) { + count(imp.getAName()) > 1 and not imp.isFromImport() +} + +from Import i +where multiple_import(i) +select i, "Multiple imports on one line." diff --git a/python/ql/src/Imports/ModuleImportsItself.py b/python/ql/src/Imports/ModuleImportsItself.py new file mode 100644 index 000000000000..0757e2c1c7fb --- /dev/null +++ b/python/ql/src/Imports/ModuleImportsItself.py @@ -0,0 +1,6 @@ +import ModuleImportsItself + +def factorial(n): + if n <= 0: + return 1 + return n * ModuleImportsItself.factorial(n - 1) \ No newline at end of file diff --git a/python/ql/src/Imports/ModuleImportsItself.qhelp b/python/ql/src/Imports/ModuleImportsItself.qhelp new file mode 100644 index 000000000000..1fbad45c1493 --- /dev/null +++ b/python/ql/src/Imports/ModuleImportsItself.qhelp @@ -0,0 +1,33 @@ + + + + + +

    There is no need for a module to import itself. A module importing itself may lead to errors as +the module may be in an incomplete state when imported by itself. +

    + +
    + +

    Remove the import statement. +Convert all expressions of the form mod.name where "mod" is the name +of the current module to name.

    + +
    + +

    In this example the module, ModuleImportsItself imports itself and has an expression +referencing the module it is in as well.

    + + +

    The import can be removed and the reference can be corrected.

    + + +
    + + +
  • Python: Modules.
  • + +
    +
    diff --git a/python/ql/src/Imports/ModuleImportsItself.ql b/python/ql/src/Imports/ModuleImportsItself.ql new file mode 100644 index 000000000000..d07d79ed9a34 --- /dev/null +++ b/python/ql/src/Imports/ModuleImportsItself.ql @@ -0,0 +1,22 @@ +/** + * @name Module imports itself + * @description A module imports itself + * @kind problem + * @tags maintainability + * useless-code + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/import-own-module + */ + +import python + +predicate modules_imports_itself(Import i, ModuleObject m) { + i.getEnclosingModule() = m.getModule() and + m.importedAs(i.getAnImportedModuleName()) +} + +from Import i, ModuleObject m +where modules_imports_itself(i, m) +select i, "The module '" + m.getName() + "' imports itself." diff --git a/python/ql/src/Imports/ModuleImportsItselfFix.py b/python/ql/src/Imports/ModuleImportsItselfFix.py new file mode 100644 index 000000000000..73f826d202fd --- /dev/null +++ b/python/ql/src/Imports/ModuleImportsItselfFix.py @@ -0,0 +1,5 @@ + +def factorial(n): + if n <= 0: + return 1 + return n * factorial(n - 1) \ No newline at end of file diff --git a/python/ql/src/Imports/ModuleLevelCyclicImport.qhelp b/python/ql/src/Imports/ModuleLevelCyclicImport.qhelp new file mode 100644 index 000000000000..30af68d364e1 --- /dev/null +++ b/python/ql/src/Imports/ModuleLevelCyclicImport.qhelp @@ -0,0 +1,40 @@ + + + +

    A cyclic import is an import which imports another module +and that module imports (possibly indirectly) the module which contains the +import statement. +If all imports in a cyclic import occur at module level, then a module will be +imported when it is part way through its initialization. This may rest in +surprising errors, as parts of the module being imported may not yet exist. +

    + +

    In addition to the possible errors, cyclic imports indicate that two modules +are circularly dependent. This means that the modules cannot be tested +independently, and it makes it harder to understand the architecture of the system. +

    + +
    + + +

    The cycle may be broken by removing any one import. If only one function or +method requires the import, then consider moving that to the other module and +deleting the import. If the two modules are more intimately connected, then move +the inter-dependent parts into a third module and have both the original modules +import that. +

    + + +
    + + + +
  • Python Language Reference: The import statement.
  • +
  • Python: Modules.
  • +
  • Effbot: Import Confusion.
  • + + +
    +
    diff --git a/python/ql/src/Imports/ModuleLevelCyclicImport.ql b/python/ql/src/Imports/ModuleLevelCyclicImport.ql new file mode 100644 index 000000000000..c7dc84e10949 --- /dev/null +++ b/python/ql/src/Imports/ModuleLevelCyclicImport.ql @@ -0,0 +1,31 @@ +/** + * @name Module-level cyclic import + * @description Module uses member of cyclically imported module, which can lead to failure at import time. + * @kind problem + * @tags reliability + * correctness + * types + * @problem.severity error + * @sub-severity low + * @precision high + * @comprehension 0.5 + * @id py/unsafe-cyclic-import + */ + +import python +import Cyclic + +// This is a potentially crashing bug if +// 1. the imports in the whole cycle are lexically outside a def (and so executed at import time) +// 2. there is a use ('M.foo' or 'from M import foo') of the imported module that is lexically outside a def +// 3. 'foo' is defined in M after the import in M which completes the cycle. +// then if we import the 'used' module, we will reach the cyclic import, start importing the 'using' +// module, hit the 'use', and then crash due to the imported symbol not having been defined yet + +from PythonModuleObject m1, Stmt imp, PythonModuleObject m2, string attr, Expr use, ControlFlowNode defn +where failing_import_due_to_cycle(m1, m2, imp, defn, use, attr) +select use, "'" + attr + "' may not be defined if module $@ is imported before module $@, " + +"as the $@ of " + attr + " occurs after the cyclic $@ of " + m2.getName() + ".", +m1, m1.getName(), m2, m2.getName(), defn, "definition", imp, "import" + + \ No newline at end of file diff --git a/python/ql/src/Imports/MultipleImports.py b/python/ql/src/Imports/MultipleImports.py new file mode 100644 index 000000000000..5897abc4d9aa --- /dev/null +++ b/python/ql/src/Imports/MultipleImports.py @@ -0,0 +1,3 @@ +import module1 +import module2 +import module1 # Duplicate import diff --git a/python/ql/src/Imports/MultipleImports.qhelp b/python/ql/src/Imports/MultipleImports.qhelp new file mode 100644 index 000000000000..40bbfe4654dd --- /dev/null +++ b/python/ql/src/Imports/MultipleImports.qhelp @@ -0,0 +1,24 @@ + + + +

    Importing the same module more than once has no effect as each module is only loaded once. It also +confuses readers of the code.

    + +
    + +

    Remove the second import.

    + +
    + + + + + + +
  • Python: import statement.
  • + + +
    +
    diff --git a/python/ql/src/Imports/MultipleImports.ql b/python/ql/src/Imports/MultipleImports.ql new file mode 100644 index 000000000000..4e5f16779c09 --- /dev/null +++ b/python/ql/src/Imports/MultipleImports.ql @@ -0,0 +1,44 @@ +/** + * @name Module is imported more than once + * @description Importing a module a second time has no effect and impairs readability + * @kind problem + * @tags maintainability + * useless-code + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/repeated-import + */ + +import python + +predicate is_simple_import(Import imp) { + not exists(Attribute a | imp.contains(a)) +} + +predicate double_import(Import original, Import duplicate, Module m) { + original != duplicate and + is_simple_import(original) and is_simple_import(duplicate) and + /* Imports import the same thing */ + exists (ImportExpr e1, ImportExpr e2 | e1.getName() = m.getName() and e2.getName() = m.getName() and + e1 = original.getAName().getValue() and e2 = duplicate.getAName().getValue() + ) and + original.getAName().getAsname().(Name).getId() = duplicate.getAName().getAsname().(Name).getId() + and + exists(Module enclosing | + original.getScope() = enclosing and + duplicate.getEnclosingModule() = enclosing and + ( + /* Duplicate is not at top level scope */ + duplicate.getScope() != enclosing + or + /* Original dominates duplicate */ + original.getAnEntryNode().dominates(duplicate.getAnEntryNode()) + ) + ) +} + +from Import original, Import duplicate, Module m +where double_import(original, duplicate, m) +select duplicate, "This import of module " + m.getName() + " is redundant, as it was previously imported $@.", + original, "on line " + original.getLocation().getStartLine().toString() diff --git a/python/ql/src/Imports/SyntaxError.qhelp b/python/ql/src/Imports/SyntaxError.qhelp new file mode 100644 index 000000000000..00b4870e86a0 --- /dev/null +++ b/python/ql/src/Imports/SyntaxError.qhelp @@ -0,0 +1,39 @@ + + + + +

    Syntax errors prevent a module being evaluated and thus imported. +An attempt to import a module with invalid syntax will fail; a SyntaxError will be raised.

    + +

    A common cause of syntax errors is the difference in syntax between Python 2 +and Python 3. In particular, a syntax error may be alerted if a Python 3 file is +assumed to be compatible with Python 2 (or vice versa). Explicitly specifying +the expected Python version can help prevent this. +

    + +

    The existence of a syntax error in a module may suggest other problems as well. +Either the module is never imported in practice and could be deleted or a +try statement around the import is mistakenly discarding the SyntaxError. +

    + + +
    + +

    Fixing the syntax error is the obvious fix. +However, it is worth investigating why a module containing a syntax error +was able to persist and address that problem as well. +

    +

    If you suspect that the syntax error is caused by the analysis using the +wrong version of Python, consider specifying the version explicitly. For +LGTM.com, you can customize extraction using an lgtm.yml file as +described here. +

    +
    + + +
  • Python Tutorial: SyntaxErrors.
  • + +
    +
    diff --git a/python/ql/src/Imports/SyntaxError.ql b/python/ql/src/Imports/SyntaxError.ql new file mode 100644 index 000000000000..677793a932aa --- /dev/null +++ b/python/ql/src/Imports/SyntaxError.ql @@ -0,0 +1,17 @@ +/** + * @name Syntax error + * @description Syntax errors cause failures at runtime and prevent analysis of the code. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity high + * @precision high + * @id py/syntax-error + */ + +import python + +from SyntaxError error +where not error instanceof EncodingError +select error, error.getMessage() + " (in Python " + major_version() + "." + minor_version() + ")." \ No newline at end of file diff --git a/python/ql/src/Imports/UnintentionalImport.py b/python/ql/src/Imports/UnintentionalImport.py new file mode 100644 index 000000000000..272fc9fd0468 --- /dev/null +++ b/python/ql/src/Imports/UnintentionalImport.py @@ -0,0 +1,14 @@ +# Example module - finance.py + +__all__ = ['tax1', 'tax2'] #defines the names to import when '*' is used + +tax1 = 5 +tax2 = 10 +def cost(): return 'cost' + +# Imported into code using + +from finance import * + +print tax1 +print tax2 \ No newline at end of file diff --git a/python/ql/src/Imports/UnintentionalImport.qhelp b/python/ql/src/Imports/UnintentionalImport.qhelp new file mode 100644 index 000000000000..8f873931e475 --- /dev/null +++ b/python/ql/src/Imports/UnintentionalImport.qhelp @@ -0,0 +1,45 @@ + + + + + +

    When you import a module using from xxx import * all public names defined in the +module are imported and bound in the local namespace of the import statement. The +public names are determined by checking the __all__ variable for the module. If +__all__ is not defined then all names within the module that do not start with an underscore +character are imported. This pollutes the current namespace with names that are not part of the +public API for the module. +

    + +
    + +

    There are two ways to address this problem:

    +
    • where possible, modify the module being imported from and define __all__ + to restrict the names to be imported
    • +
    • otherwise, explicitly import the values that you need.
    • +
    + +
    + +

    The following simple example shows how __all__ controls the public names for the +module finance.

    + + +

    If the finance module did not include a definition of __all__, then you +could replace from finance import * with from finance import tax1, tax2. +

    + +
    + + +
  • Python Language Reference: The import statement. +
  • +
  • Python Tutorial: Modules.
  • + + + + +
    +
    diff --git a/python/ql/src/Imports/UnintentionalImport.ql b/python/ql/src/Imports/UnintentionalImport.ql new file mode 100644 index 000000000000..3815b04f64a9 --- /dev/null +++ b/python/ql/src/Imports/UnintentionalImport.ql @@ -0,0 +1,32 @@ +/** + * @name 'import *' may pollute namespace + * @description Importing a module using 'import *' may unintentionally pollute the global + * namespace if the module does not define '__all__' + * @kind problem + * @tags maintainability + * modularity + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/polluting-import + */ + +import python + +predicate import_star(ImportStar imp, ModuleObject exporter) { + exporter.importedAs(imp.getImportedModuleName()) +} + +predicate all_defined(ModuleObject exporter) { + exporter.isC() + or + exporter.getModule().(ImportTimeScope).definesName("__all__") + or + exporter.getModule().getInitModule().(ImportTimeScope).definesName("__all__") +} + + +from ImportStar imp, ModuleObject exporter +where import_star(imp, exporter) and not all_defined(exporter) +select imp, "Import pollutes the enclosing namespace, as the imported module $@ does not define '__all__'.", + exporter, exporter.getName() diff --git a/python/ql/src/Imports/UnusedImport.qhelp b/python/ql/src/Imports/UnusedImport.qhelp new file mode 100644 index 000000000000..8edd5dcc7ec7 --- /dev/null +++ b/python/ql/src/Imports/UnusedImport.qhelp @@ -0,0 +1,23 @@ + + + + + +

    A module is imported (using the import statement) but that module +is never used. This creates a dependency that does not need to exist and makes the code +more difficult to read. +

    + +
    + +

    Delete the import statement.

    + +
    + + +
  • Python: import statement.
  • + +
    +
    diff --git a/python/ql/src/Imports/UnusedImport.ql b/python/ql/src/Imports/UnusedImport.ql new file mode 100644 index 000000000000..7d6bd1b6805f --- /dev/null +++ b/python/ql/src/Imports/UnusedImport.ql @@ -0,0 +1,74 @@ +/** + * @name Unused import + * @description Import is not required as it is not used + * @kind problem + * @tags maintainability + * useless-code + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/unused-import + */ + +import python +import Variables.Definition + +predicate global_name_used(Module m, Variable name) { + exists (Name u, GlobalVariable v | + u.uses(v) and + v.getId() = name.getId() and + u.getEnclosingModule() = m + ) + or + /* A use of an undefined class local variable, will use the global variable */ + exists(Name u, LocalVariable v | + u.uses(v) and + v.getId() = name.getId() and + u.getEnclosingModule() = m and + not v.getScope().getEnclosingScope*() instanceof Function + ) +} + +/** Holds if a module has `__all__` but we don't understand it */ +predicate all_not_understood(Module m) { + exists(GlobalVariable a | + a.getId() = "__all__" and a.getScope() = m | + /* __all__ is not defined as a simple list */ + not m.declaredInAll(_) + or + /* __all__ is modified */ + exists(Call c | c.getFunc().(Attribute).getObject() = a.getALoad()) + ) +} + +predicate unused_import(Import imp, Variable name) { + ((Name)imp.getAName().getAsname()).getVariable() = name + and + not imp.getAnImportedModuleName() = "__future__" + and + not imp.getEnclosingModule().declaredInAll(name.getId()) + and + imp.getScope() = imp.getEnclosingModule() + and + not global_name_used(imp.getScope(), name) + and + /* Imports in __init__.py are used to force module loading */ + not imp.getEnclosingModule().isPackageInit() + and + /* Name may be imported for use in epytext documentation */ + not exists(Comment cmt | + cmt.getText().matches("%L{" + name.getId() + "}%") | + cmt.getLocation().getFile() = imp.getLocation().getFile() + ) + and + not name_acceptable_for_unused_variable(name) + and + /* Assume that opaque `__all__` includes imported module */ + not all_not_understood(imp.getEnclosingModule()) +} + + +from Stmt s, Variable name +where unused_import(s, name) +select s, "Import of '" + name.getId() + "' is not used." + diff --git a/python/ql/src/Imports/from_import.py b/python/ql/src/Imports/from_import.py new file mode 100644 index 000000000000..48151af5fce3 --- /dev/null +++ b/python/ql/src/Imports/from_import.py @@ -0,0 +1,5 @@ +from sys import stdout + +def main(): + stdout.write("Hello World!") + diff --git a/python/ql/src/Imports/from_import_fixed.py b/python/ql/src/Imports/from_import_fixed.py new file mode 100644 index 000000000000..a93c93f87010 --- /dev/null +++ b/python/ql/src/Imports/from_import_fixed.py @@ -0,0 +1,4 @@ +import sys + +def main(): + sys.stdout.write("Hello World!") diff --git a/python/ql/src/Imports/redirect.py b/python/ql/src/Imports/redirect.py new file mode 100644 index 000000000000..00a4275bfaa6 --- /dev/null +++ b/python/ql/src/Imports/redirect.py @@ -0,0 +1,11 @@ +import sys + +def redirect_to_file(function, args, kwargs, filename): + with open(filename) as out: + orig_stdout = sys.stdout + sys.stdout = out + try: + function(*args, **kwargs) + finally: + sys.stdout = orig_stdout + diff --git a/python/ql/src/Lexical/CommentedOutCode.py b/python/ql/src/Lexical/CommentedOutCode.py new file mode 100644 index 000000000000..6e7baa6366b9 --- /dev/null +++ b/python/ql/src/Lexical/CommentedOutCode.py @@ -0,0 +1,4 @@ +def area(r): + #if DEBUG: + # print("Computing area of %r" % r) + return r.length * r.width diff --git a/python/ql/src/Lexical/CommentedOutCode.qhelp b/python/ql/src/Lexical/CommentedOutCode.qhelp new file mode 100644 index 000000000000..0d153665cdc9 --- /dev/null +++ b/python/ql/src/Lexical/CommentedOutCode.qhelp @@ -0,0 +1,7 @@ + + + + + diff --git a/python/ql/src/Lexical/CommentedOutCode.ql b/python/ql/src/Lexical/CommentedOutCode.ql new file mode 100644 index 000000000000..52633f8e3000 --- /dev/null +++ b/python/ql/src/Lexical/CommentedOutCode.ql @@ -0,0 +1,20 @@ +/** + * @name Commented out code + * @description Commented out code causes visual clutter as it is neither code nor comment. + * @kind problem + * @tags maintainability + * readability + * documentation + * @problem.severity recommendation + * @sub-severity high + * @precision high + * @id py/commented-out-code + */ + +import python + +import Lexical.CommentedOutCode + +from CommentedOutCodeBlock c +where not c.maybeExampleCode() +select c, "These comments appear to contain commented-out code." diff --git a/python/ql/src/Lexical/CommentedOutCode.qll b/python/ql/src/Lexical/CommentedOutCode.qll new file mode 100644 index 000000000000..dbb128674663 --- /dev/null +++ b/python/ql/src/Lexical/CommentedOutCode.qll @@ -0,0 +1,337 @@ +import python + + +private predicate def_statement(Comment c) { + c.getText().regexpMatch("#(\\S*\\s+)?def\\s.*\\(.*\\).*:\\s*(#.*)?") +} + +private predicate if_statement(Comment c) { + c.getText().regexpMatch("#(\\S*\\s+)?(el)?if\\s.*:\\s*(#.*)?") + or + c.getText().regexpMatch("#(\\S*\\s+)?else:\\s*(#.*)?") +} + +private predicate for_statement(Comment c) { + c.getText().regexpMatch("#(\\S*\\s+)?for\\s.*\\sin\\s.*:\\s*(#.*)?") +} + +private predicate with_statement(Comment c) { + c.getText().regexpMatch("#(\\S*\\s+)?with\\s+.*:\\s*(#.*)?") +} + +private predicate try_statement(Comment c) { + c.getText().regexpMatch("#(\\S*\\s+)?try:\\s*(#.*)?") + or + c.getText().regexpMatch("#(\\S*\\s+)?except\\s*(\\w+\\s*(\\sas\\s+\\w+\\s*)?)?:\\s*(#.*)?") + or + c.getText().regexpMatch("#(\\S*\\s+)?finally:\\s*(#.*)?") +} + +private int indentation(Comment c) { + exists(int offset | + maybe_code(c) and + exists(c.getText().regexpFind("[^\\s#]", 1, offset)) and + result = offset + c.getLocation().getStartColumn() + ) +} + +private predicate class_statement(Comment c) { + c.getText().regexpMatch("#(\\S*\\s+)?class\\s+\\w+.*:\\s*(#.*)?") +} + +private predicate triple_quote(Comment c) { + c.getText().regexpMatch("#.*(\"\"\"|''').*") +} + +private predicate triple_quoted_string_part(Comment start, Comment end) { + triple_quote(start) and end = start + or + exists(Comment mid | + triple_quoted_string_part(start, mid) and + end = non_empty_following(mid) and + not triple_quote(end) + ) +} + +private predicate maybe_code(Comment c) { + not non_code(c) and not filler(c) and not endline_comment(c) and not file_or_url(c) + or + commented_out_comment(c) +} + +private predicate commented_out_comment(Comment c) { + c.getText().regexpMatch("#+\\s+#.*") +} + +private int scope_start(Comment start) { + ( + def_statement(start) or + class_statement(start) + ) + and + result = indentation(start) + and + not non_code(start) +} + +private int block_start(Comment start) { + ( + if_statement(start) or + for_statement(start) or + try_statement(start) or + with_statement(start) + ) + and + result = indentation(start) + and + not non_code(start) +} + +private int scope_doc_string_part(Comment start, Comment end) { + result = scope_start(start) and + triple_quote(end) and end = non_empty_following(start) + or + exists(Comment mid | + result = scope_doc_string_part(start, mid) and + end = non_empty_following(mid) | + not triple_quote(end) + ) +} + +private int scope_part(Comment start, Comment end) { + result = scope_start(start) and end = start + or + exists(Comment mid | + result = scope_doc_string_part(start, mid) and + end = non_empty_following(mid) and + triple_quote(end) + ) + or + exists(Comment mid | + result = scope_part(start, mid) and + end = non_empty_following(mid) | + indentation(end) > result + ) +} + +private int block_part(Comment start, Comment end) { + result = block_start(start) and + end = non_empty_following(start) and + indentation(end) > result + or + exists(Comment mid | + result = block_part(start, mid) and + end = non_empty_following(mid) | + indentation(end) > result + or + result = block_start(end) + ) +} + +private predicate commented_out_scope_part(Comment start, Comment end) { + exists(scope_doc_string_part(start, end)) + or + exists(scope_part(start, end)) +} + +private predicate commented_out_code(Comment c) { + commented_out_scope_part(c, _) + or + commented_out_scope_part(_, c) + or + exists(block_part(c, _)) + or + exists(block_part(_, c)) +} + +private predicate commented_out_code_part(Comment start, Comment end) { + commented_out_code(start) and end = start and + not exists(Comment prev | + non_empty_following(prev) = start | + commented_out_code(prev) + ) + or + exists(Comment mid | + commented_out_code_part(start, mid) and + non_empty_following(mid) = end and + commented_out_code(end) + ) +} + +private predicate commented_out_code_block(Comment start, Comment end) { + /* A block must be at least 2 comments long. */ + start != end and + commented_out_code_part(start, end) and + not commented_out_code(non_empty_following(end)) +} + +/* A single line comment that appears to be commented out code */ +class CommentedOutCodeLine extends Comment { + + CommentedOutCodeLine () { + exists(CommentedOutCodeBlock b | + b.contains(this) + ) + } + + /* Whether this commented-out code line is likely to be example code embedded in a larger comment. */ + predicate maybeExampleCode() { + exists(CommentedOutCodeBlock block | + block.contains(this) and + block.maybeExampleCode() + ) + } + +} + +/** A block of comments that appears to be commented out code */ +class CommentedOutCodeBlock extends @py_comment { + + CommentedOutCodeBlock() { + commented_out_code_block(this, _) + } + + string toString() { + result = "Commented out code" + } + + /** Whether this commented-out code block contains the comment c */ + predicate contains(Comment c) { + this = c + or + exists(Comment prev | + non_empty_following(prev) = c and + not commented_out_code_block(this, prev) and + this.contains(prev) + ) + } + + /** The length of this comment block (in comments) */ + int length() { + result = count(Comment c | this.contains(c)) + } + + predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { + ((Comment)this).getLocation().hasLocationInfo(filepath, bl, bc, _, _) + and + exists(Comment end | + commented_out_code_block(this, end) | + end.getLocation().hasLocationInfo(_, _, _, el, ec) + ) + } + + /** Whether this commented-out code block is likely to be example code embedded in a larger comment. */ + predicate maybeExampleCode() { + exists(CommentBlock block | + block.contains((Comment)this) | + exists(int all_code | + all_code = sum (CommentedOutCodeBlock code | block.contains((Comment)code) | code.length()) + and + /* This ratio may need fine tuning */ + block.length() > all_code*2 + ) + ) + } +} + +/** Does c contain the pair of words "s1 s2" with only whitespace between them */ +private predicate word_pair(Comment c, string s1, string s2) { + exists(int i1, int i2, int o1, int o2 | + s1 = c.getText().regexpFind("\\w+", i1, o1) and + s2 = c.getText().regexpFind("\\w+", i2, o2) and + i2 = i1 + 1 and + c.getText().prefix(o1).regexpMatch("[^'\"]*") and + c.getText().substring(o1 + s1.length(), o2).regexpMatch("\\s+") + ) +} + +/** The comment c cannot be code if it contains a word pair "word1 word2" and + * either: + * 1. word1 is not a keyword and word2 is not an operator: + * "x is" could be code, "return y" could be code, but "isnt code" cannot be code. + * or + * 2. word1 is a keyword requiring a colon and there is no colon: + * "with spam" can only be code if the comment contains a colon. + */ +private predicate non_code(Comment c) { + exists(string word1, string word2 | + word_pair(c, word1, word2) and + not word2 = operator_keyword() + | + not word1 = a_keyword() + or + word1 = keyword_requiring_colon() and not c.getText().matches("%:%") + ) and + /* Except comments of the form: # (maybe code) # some comment */ + not c.getText().regexpMatch("#\\S+\\s.*#.*") + or + /* Don't count doctests as code */ + c.getText().matches("%>>>%") or c.getText().matches("%...%") +} + +private predicate filler(Comment c) { + c.getText().regexpMatch("#+[\\s*#-_=+]*") +} + +/** Gets the first non empty comment following c */ +private Comment non_empty_following(Comment c) { + not empty(result) and + ( + result = empty_following(c).getFollowing() + or + not empty(c) and result = c.getFollowing() + ) +} + +/* Helper for non_empty_following() */ +private Comment empty_following(Comment c) { + not empty(c) and + empty(result) + and + exists(Comment prev | + result = prev.getFollowing() | + prev = c + or + prev = empty_following(c) + ) +} + +private predicate empty(Comment c) { + c.getText().regexpMatch("#+\\s*") +} + +/* A comment following code on the same line */ +private predicate endline_comment(Comment c) { + exists(Expr e, string f, int line | + e.getLocation().hasLocationInfo(f, line, _, _, _) and + c.getLocation().hasLocationInfo(f, line, _, _, _) + ) +} + +private predicate file_or_url(Comment c) { + c.getText().regexpMatch("#[^'\"]+(https?|file)://.*") or + c.getText().regexpMatch("#[^'\"]+(/[a-zA-Z]\\w*)+\\.[a-zA-Z]+.*") or + c.getText().regexpMatch("#[^'\"]+(\\[a-zA-Z]\\w*)+\\.[a-zA-Z]+.*") +} + +private string operator_keyword() { + result = "import" or result = "and" or result = "is" or result = "or" or result = "in" or result = "not" or result = "as" +} + +private string keyword_requiring_colon() { + result = "try" or result = "while" or result = "elif" or result = "else" or result = "if" or + result = "except" or result = "def" or result = "class" +} + +private string other_keyword() { + result = "del" or result = "lambda" or result = "from" or + result = "global" or result = "with" or result = "assert" or + result = "yield" or result = "finally" or + result = "print" or + result = "exec" or result = "raise" or + result = "return" or result = "for" +} + +private string a_keyword() { + result = keyword_requiring_colon() or result = other_keyword() or result = operator_keyword() +} diff --git a/python/ql/src/Lexical/CommentedOutCodeCommon.qhelp b/python/ql/src/Lexical/CommentedOutCodeCommon.qhelp new file mode 100644 index 000000000000..aeae8991726a --- /dev/null +++ b/python/ql/src/Lexical/CommentedOutCodeCommon.qhelp @@ -0,0 +1,28 @@ + + + + +

    +Remove the commented-out code, or reinstate it if necessary. If you want to include a snippet +of example code in a comment, consider adding an @example tag or enclosing the code +in a code or pre element. +

    + +
    + +

    +In the following example, a print statement, originally used +for debugging, is left in the code, but commented out. It should be removed altogether. +

    + + + +
    + + +
  • Los Techies: Commented Code == Technical Debt.
  • + +
    +
    diff --git a/python/ql/src/Lexical/FCommentedOutCode.qhelp b/python/ql/src/Lexical/FCommentedOutCode.qhelp new file mode 100644 index 000000000000..2caeaf5cdf39 --- /dev/null +++ b/python/ql/src/Lexical/FCommentedOutCode.qhelp @@ -0,0 +1,7 @@ + + + + + diff --git a/python/ql/src/Lexical/FCommentedOutCode.ql b/python/ql/src/Lexical/FCommentedOutCode.ql new file mode 100644 index 000000000000..2f6ee0741c6a --- /dev/null +++ b/python/ql/src/Lexical/FCommentedOutCode.ql @@ -0,0 +1,20 @@ +/** + * @name Lines of commented-out code in files + * @description The number of lines of commented out code per file + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @precision high + * @tags maintainability + * @id py/lines-of-commented-out-code-in-files + */ + +import python +import Lexical.CommentedOutCode + +import python + +from File f, int n +where n = count(CommentedOutCodeLine c | not c.maybeExampleCode() and c.getLocation().getFile() = f) +select f, n +order by n desc diff --git a/python/ql/src/Lexical/OldOctalLiteral.py b/python/ql/src/Lexical/OldOctalLiteral.py new file mode 100644 index 000000000000..ad15ab8889b3 --- /dev/null +++ b/python/ql/src/Lexical/OldOctalLiteral.py @@ -0,0 +1,12 @@ + +#Easily misread as x = 15 +x = 015 + +#The extra 'o' alerts the reader that this is an octal literal +y = 0o15 + +#If this is a byte sized value then a hexadecimal might be clearer +y = 0x0d + +#Or if it is a bit pattern then a binary value might be clearer +y = 0b1101 diff --git a/python/ql/src/Lexical/OldOctalLiteral.qhelp b/python/ql/src/Lexical/OldOctalLiteral.qhelp new file mode 100644 index 000000000000..f44bbddbc07c --- /dev/null +++ b/python/ql/src/Lexical/OldOctalLiteral.qhelp @@ -0,0 +1,35 @@ + + + +

    +Octal literals starting with 0 are easily misread as a decimal, +particularly by those programmers who do not have a C or Java background. +

    + +

    +The new literal syntax for non-decimal numbers is more distinct and is thus less likely to be misunderstood. +

    + +
    + + +

    +Use the 0oXXX form instead of the 0XXX form. Alternatively use binary or hexadecimal format if that would be clearer. +

    + +
    + + + + + + + +
  • Python Language Reference: Integer Literals.
  • +
  • Python PEP 3127: Integer Literal Support and Syntax.
  • + + +
    +
    diff --git a/python/ql/src/Lexical/OldOctalLiteral.ql b/python/ql/src/Lexical/OldOctalLiteral.ql new file mode 100644 index 000000000000..af0ee723c10d --- /dev/null +++ b/python/ql/src/Lexical/OldOctalLiteral.ql @@ -0,0 +1,31 @@ +/** + * @name Confusing octal literal + * @description Octal literal with a leading 0 is easily misread as a decimal value + * @kind problem + * @tags readability + * @problem.severity recommendation + * @sub-severity low + * @precision high + * @id py/old-style-octal-literal + */ + +import python + +predicate is_old_octal(IntegerLiteral i) { + exists(string text | + text = i.getText() | + text.charAt(0) = "0" and + not text = "00" and + exists(text.charAt(1).toInt()) and + /* Do not flag file permission masks */ + exists(int len | len = text.length() | + len != 4 and + len != 5 and + len != 7 + ) + ) +} + +from IntegerLiteral i +where is_old_octal(i) +select i, "Confusing octal literal, use 0o" + i.getText().suffix(1) + " instead." diff --git a/python/ql/src/Lexical/ToDoComment.py b/python/ql/src/Lexical/ToDoComment.py new file mode 100644 index 000000000000..3cc021e6d15b --- /dev/null +++ b/python/ql/src/Lexical/ToDoComment.py @@ -0,0 +1,8 @@ +def realpath(path): + ''' + Returns the true, canonical file system path equivalent to the given + path. + ''' + # TODO: There may be a more clever way to do this that also handles other, + # less common file systems. + return os.path.normpath(normcase(os.path.realpath(path))) \ No newline at end of file diff --git a/python/ql/src/Lexical/ToDoComment.qhelp b/python/ql/src/Lexical/ToDoComment.qhelp new file mode 100644 index 000000000000..2b5154d5924a --- /dev/null +++ b/python/ql/src/Lexical/ToDoComment.qhelp @@ -0,0 +1,53 @@ + + + + +

    A comment that includes the word TODO often marks a part of +the code that is incomplete or broken, or highlights ambiguities in the +software's specification.

    + +

    For example, this list of comments is typical of those found in real +programs:

    + +
      +
    • TODO: move this code somewhere else
    • +
    • TODO: find a better solution to this workaround
    • +
    • TODO: test this
    • +
    + +
    + + +

    It is very important that TODO comments are +not just removed from the code. Each of them must be addressed in some way.

    + +

    Simpler comments can usually be immediately addressed by fixing the code, +adding a test, doing some refactoring, or clarifying the intended behavior of +a feature.

    + +

    In contrast, larger issues may require discussion, and a significant amount +of work to address. In these cases it is a good idea to move the comment to an +issue-tracking system, so that the issue can be tracked +and prioritized relative to other defects and feature requests.

    + +
    + +

    The following example shows a function where a TODO comment indicates a known limitation in the +existing implementation. The function should be reviewed, the limitation addressed and then the +comment deleted.

    + + + +
    + + +
  • + Wikipedia: + Comment tags. +
  • + + +
    +
    diff --git a/python/ql/src/Lexical/ToDoComment.ql b/python/ql/src/Lexical/ToDoComment.ql new file mode 100644 index 000000000000..b8bcf98ada8c --- /dev/null +++ b/python/ql/src/Lexical/ToDoComment.ql @@ -0,0 +1,21 @@ +/** + * @name 'To Do' comment + * @description Writing comments that include 'TODO' tends to lead to a build up of partially + * implemented features. + * @kind problem + * @tags maintainability + * readability + * documentation + * external/cwe/cwe-546 + * @problem.severity recommendation + * @sub-severity low + * @deprecated + * @precision medium + * @id py/todo-comment + */ + +import python + +from Comment c +where c.getText().matches("%TODO%") or c.getText().matches("%TO DO%") +select c, c.getText() diff --git a/python/ql/src/Metrics/CLinesOfCode.qhelp b/python/ql/src/Metrics/CLinesOfCode.qhelp new file mode 100644 index 000000000000..666bd473cb44 --- /dev/null +++ b/python/ql/src/Metrics/CLinesOfCode.qhelp @@ -0,0 +1,23 @@ + + + +

    This metric measures the number of lines of code in a function. This excludes comments and blank lines.

    + +

    Having too many lines of code in a function is an indication that it can be split into several functions of more manageable size.

    + +
    + + +

    Long functions should be examined to see if they can be split into smaller, more cohesive functions.

    + +
    + + +
  • M. Fowler, Refactoring. Addison-Wesley, 1999.
  • +
  • Wikipedia: Code refactoring.
  • + + +
    +
    diff --git a/python/ql/src/Metrics/CLinesOfCode.ql b/python/ql/src/Metrics/CLinesOfCode.ql new file mode 100644 index 000000000000..5c5453fb76a2 --- /dev/null +++ b/python/ql/src/Metrics/CLinesOfCode.ql @@ -0,0 +1,15 @@ +/** + * @name Lines of code in functions + * @description The number of lines of code in a function. + * @kind treemap + * @id py/lines-of-code-per-function + * @treemap.warnOn highValues + * @metricType callable + * @metricAggregate avg sum max + * @tags maintainability + */ +import python + +from Function f +select f, f.getMetrics().getNumberOfLinesOfCode() as n +order by n desc \ No newline at end of file diff --git a/python/ql/src/Metrics/ClassAfferentCoupling.qhelp b/python/ql/src/Metrics/ClassAfferentCoupling.qhelp new file mode 100644 index 000000000000..a1e2792c0469 --- /dev/null +++ b/python/ql/src/Metrics/ClassAfferentCoupling.qhelp @@ -0,0 +1,80 @@ + + + +

    +This metric measures the number of incoming dependencies for each +class, that is the number of other classes that depend on it. +

    + +

    +Classes that are depended upon by many other classes typically require a lot of +effort to change, because changing them will force their dependents to change +as well. This is not necessarily a bad thing -- indeed, most systems will have +some such classes (one example might be a string class). However, classes with a high number +of incoming dependencies +and a high number of outgoing dependencies are hard to maintain. A class with both high afferent +coupling and high efferent coupling is referred to as a hub class. +Such classes can be problematic, because on the one hand they are hard to +change (high afferent coupling), yet on the other they have many reasons to +change (high efferent coupling). This contradiction yields code that is very +hard to maintain or test. +

    + +

    +Conversely, some classes may only be depended on by very few other classes. Again, +this is not necessarily a problem -- we would expect, for example, that the +top-level classes of a system would meet this criterion. When lower-level +classes have very few incoming dependencies, however, it can be an indication +that a class is not pulling its weight. In extreme cases, classes may even +have an afferent coupling of 0, indicating that they are dead +code. +

    + +
    + + +

    +It is unwise to refactor a class based purely on its high or low number of +incoming dependencies -- a class's afferent coupling value only makes sense +in the context of its role in the system as a whole. However, when combined +with other metrics such as efferent coupling, it is possible to make some +general recommendations: +

    + +
      +
    • +Classes with high numbers of incoming and outgoing dependencies +are hub classes that are prime candidates for refactoring (although this +will not always be easy). The general strategy is to split the class into +smaller classes that each have fewer responsibilities, and refactor the code +that previously used the hub class accordingly. +
    • + +
    • +Classes that have very few incoming dependencies and are not at the top level +of a system may not be pulling their weight and should be refactored, e.g. +using the 'Collapse Hierarchy' or 'Inline Class' techniques in [Fowler] +(see the section entitled 'Lazy Class' on p.68). +
    • + +
    • +Classes that have an afferent coupling of 0 may be dead code -- +in this situation, they can often be deleted. +
    • +
    + + + +
    + + + +
  • +M. Fowler. Refactoring. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/ClassAfferentCoupling.ql b/python/ql/src/Metrics/ClassAfferentCoupling.ql new file mode 100644 index 000000000000..5fd2ec4c16f6 --- /dev/null +++ b/python/ql/src/Metrics/ClassAfferentCoupling.ql @@ -0,0 +1,18 @@ +/** + * @name Incoming class dependencies + * @description The number of classes that depend on a class. + * @kind treemap + * @id py/afferent-coupling-per-class + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + * @tags changeability + * modularity + */ + +import python + +from ClassMetrics cls +select cls, cls.getAfferentCoupling() as n +order by n desc + diff --git a/python/ql/src/Metrics/ClassEfferentCoupling.py b/python/ql/src/Metrics/ClassEfferentCoupling.py new file mode 100644 index 000000000000..346388262345 --- /dev/null +++ b/python/ql/src/Metrics/ClassEfferentCoupling.py @@ -0,0 +1,10 @@ +class X: + + def iUseY(y): + y.doStuff() + + def soDoY(): + return Y() + + def iUseZ(z1, z2): + return z1.combine(z2) diff --git a/python/ql/src/Metrics/ClassEfferentCoupling.qhelp b/python/ql/src/Metrics/ClassEfferentCoupling.qhelp new file mode 100644 index 000000000000..b28d6e5fb2e9 --- /dev/null +++ b/python/ql/src/Metrics/ClassEfferentCoupling.qhelp @@ -0,0 +1,60 @@ + + + +

    +Efferent coupling is the number of outgoing dependencies for each class. In other words, it is the +number of other classes on which each class depends. +

    + +

    +A class that depends on many other classes is quite brittle, because if any of +its dependencies change, the class itself may have to change as well. Furthermore, the +reason for the high number of dependencies is often that different parts of +the class depend on different groups of other classes, so it is common to +find that classes with high efferent coupling also lack cohesion. +

    + +
    + + +

    +You can reduce efferent coupling by splitting up a class so that each part depends on fewer classes. +

    + +
    + + +

    In the following example, class X depends on both Y and +Z. +

    + + + +

    However, the methods that use Y do not use Z, and the methods +that use Z do not use Y. Therefore, the class can be split into +two classes, one of which depends only on Y and the other only on Z

    + + + +

    +Although this is a slightly artificial example, this sort of situation +does tend to occur in more complicated classes, +so the general technique is quite widely applicable. +

    + +
    + + + +
  • +IBM developerWorks: Evolutionary architecture and emergent design: Emergent design through metrics. +
  • +
  • +R. Martin, Agile Software Development: Principles, Patterns and Practices. Pearson, 2011. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/ClassEfferentCoupling.ql b/python/ql/src/Metrics/ClassEfferentCoupling.ql new file mode 100644 index 000000000000..d8d9dabd5dd3 --- /dev/null +++ b/python/ql/src/Metrics/ClassEfferentCoupling.ql @@ -0,0 +1,18 @@ +/** + * @name Outgoing class dependencies + * @description The number of classes that this class depends upon. + * @kind treemap + * @id py/efferent-coupling-per-class + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + * @tags testability + * modularity + */ + +import python + +from ClassMetrics cls +select cls, cls.getEfferentCoupling() as n +order by n desc + diff --git a/python/ql/src/Metrics/ClassEfferentCouplingGood.py b/python/ql/src/Metrics/ClassEfferentCouplingGood.py new file mode 100644 index 000000000000..1131ee6b5dd8 --- /dev/null +++ b/python/ql/src/Metrics/ClassEfferentCouplingGood.py @@ -0,0 +1,12 @@ +class YX: + + def iUseY(y): + y.doStuff() + + def soDoY(): + return Y() + +class ZX: + + def iUseZ(z1, z2): + return z1.combine(z2) diff --git a/python/ql/src/Metrics/CommentRatio.qhelp b/python/ql/src/Metrics/CommentRatio.qhelp new file mode 100644 index 000000000000..32271ccbb85a --- /dev/null +++ b/python/ql/src/Metrics/CommentRatio.qhelp @@ -0,0 +1,34 @@ + + + +

    This metric measures the percentage of lines in a file that contain a comment or are part of a +multi-line comment. Note that this metric ignores docstrings.

    + +

    The percentage of comment lines should always be considered with the value for the related metric +"Percentage of docstrings". For public modules, functions, classes and methods docstrings are the +preferred method of documentation because the information can be inspected by the program at runtime, +for example, as an interactive help system or as metadata for a function.

    + +

    Having a low percentage of comments and docstrings is an indication that a file does not have +sufficient documentation. Undocumented code is difficult to understand, modify, and reuse.

    + +
    + +

    Add documentation to files with a low comment and docstring ratio. Use docstrings to document +public modules, functions, classes and methods.

    + +
    + + +
  • Wikipedia: +Need for comments.
  • +
  • Python PEP 8: Comments.
  • +
  • Python for Beginners: +Python Docstrings.
  • +
  • Python PEP 257: Docstring Conventions.
  • + + +
    +
    diff --git a/python/ql/src/Metrics/CommentRatio.ql b/python/ql/src/Metrics/CommentRatio.ql new file mode 100644 index 000000000000..3f04da282834 --- /dev/null +++ b/python/ql/src/Metrics/CommentRatio.ql @@ -0,0 +1,18 @@ +/** + * @name Percentage of comments + * @description The percentage of lines in a file that contain comments. Note that docstrings are + * reported by a separate metric. + * @kind treemap + * @id py/comment-ratio-per-file + * @treemap.warnOn lowValues + * @metricType file + * @metricAggregate avg max + * @tags maintainability + * documentation + */ +import python + +from Module m, ModuleMetrics mm +where mm = m.getMetrics() and mm.getNumberOfLines() > 0 +select m, 100.0 * ((float)mm.getNumberOfLinesOfComments() / (float)mm.getNumberOfLines()) as ratio +order by ratio desc diff --git a/python/ql/src/Metrics/CyclomaticComplexity.qhelp b/python/ql/src/Metrics/CyclomaticComplexity.qhelp new file mode 100644 index 000000000000..0335580c4b83 --- /dev/null +++ b/python/ql/src/Metrics/CyclomaticComplexity.qhelp @@ -0,0 +1,38 @@ + + + + + +

    This metric measures the total cyclomatic complexity for the functions in a file. +

    + +

    +Cyclomatic complexity approximates the number of paths that can be taken during the execution of a +function (and hence, the minimum number of tests cases necessary to test it thoroughly). Straight-line +code has zero cyclomatic complexity, while branches and loops increase cyclomatic complexity.

    + +

    Files that contain too many complex functions can be difficult to test, understand, and maintain.

    + +
    + +

    Try to simplify overly-complex code. For example:

    + +
    • Highly nested conditionals can be simplified by rethinking the requirements that the function fulfills.
    • +
    • Repeated tests can be refactored into helper functions, which also decreases the risk of +introducing defects by copying and pasting code.
    • +
    • Large complex functions can often be split into smaller more focused functions.
    • +
    + + +
    + + +
  • M. Fowler. Refactoring. Addison-Wesley, 1999.
  • +
  • T. J. McCabe. A Complexity Measure. IEEE Transactions on Software Engineering, SE-2(4), +December 1976.
  • +
  • Wikipedia: Cyclomatic complexity.
  • + +
    +
    diff --git a/python/ql/src/Metrics/CyclomaticComplexity.ql b/python/ql/src/Metrics/CyclomaticComplexity.ql new file mode 100644 index 000000000000..c5ab98582020 --- /dev/null +++ b/python/ql/src/Metrics/CyclomaticComplexity.ql @@ -0,0 +1,19 @@ +/** + * @name Cyclomatic complexity of functions + * @description The cyclomatic complexity per function (an indication of how many tests are necessary, + * based on the number of branching statements). + * @kind treemap + * @id py/cyclomatic-complexity-per-function + * @treemap.warnOn highValues + * @metricType callable + * @metricAggregate avg max sum + * @tags testability + * complexity + * maintainability + */ +import python + +from Function func, int complexity +where complexity = func.getMetrics().getCyclomaticComplexity() +select func, complexity +order by complexity desc \ No newline at end of file diff --git a/python/ql/src/Metrics/Dependencies/ExternalDependencies.ql b/python/ql/src/Metrics/Dependencies/ExternalDependencies.ql new file mode 100644 index 000000000000..49506b0a0f9f --- /dev/null +++ b/python/ql/src/Metrics/Dependencies/ExternalDependencies.ql @@ -0,0 +1,44 @@ +/** + * @name External dependencies + * @description Count the number of dependencies that a Python source file has on external packages. + * @kind treemap + * @treemap.warnOn highValues + * @metricType externalDependency + * @precision medium + * @id py/external-dependencies + */ + +import python +import semmle.python.dependencies.TechInventory + +/* + * These two columns encode four logical columns: + * + * 1. Python source file where the dependency originates + * 2. Package Object, ideally referring to a PyPI or similar externally provided package + * 3. Version of that package Object, if known + * 4. Number of dependencies from the source file to the package + * + * Ideally this query would therefore return three columns, + * but this would require changing the dashboard database schema + * and dashboard extractor. + * + * The first column (the Python source file) is prepended with a '/' + * so that the file path matches the path used for the file in the + * dashboard database, which is implicitly relative to the source + * archive location. + */ + +predicate src_package_count(File sourceFile, ExternalPackage package, int total) { + total = strictcount(AstNode src | + dependency(src, package) and + src.getLocation().getFile() = sourceFile + ) +} + +from File sourceFile, int total, string entity, ExternalPackage package +where +src_package_count(sourceFile, package, total) and +entity = munge(sourceFile, package) +select entity, total +order by total desc diff --git a/python/ql/src/Metrics/Dependencies/ExternalDependenciesSourceLinks.ql b/python/ql/src/Metrics/Dependencies/ExternalDependenciesSourceLinks.ql new file mode 100644 index 000000000000..3129edd63284 --- /dev/null +++ b/python/ql/src/Metrics/Dependencies/ExternalDependenciesSourceLinks.ql @@ -0,0 +1,26 @@ +/** + * @name External dependency source links + * @kind source-link + * @metricType externalDependency + * @id py/dependency-source-links + */ + +import python +import semmle.python.dependencies.TechInventory + +/* + * This query creates the source links for the ExternalDependencies.ql query. + * Although the entities in question are of the form '/file/path<|>dependency', the + * /file/path is a bare string relative to the root of the source archive, and not + * tied to a particular revision. We need the File entity (the second column here) to + * recover that information once we are in the dashboard database, using the + * ExternalEntity.getASourceLink() method. + */ +from File sourceFile, string entity +where + exists(PackageObject package, AstNode src | + dependency(src, package) and + src.getLocation().getFile() = sourceFile and + entity = munge(sourceFile, package) + ) +select entity, sourceFile diff --git a/python/ql/src/Metrics/DirectImports.qhelp b/python/ql/src/Metrics/DirectImports.qhelp new file mode 100644 index 000000000000..db74ac2076fb --- /dev/null +++ b/python/ql/src/Metrics/DirectImports.qhelp @@ -0,0 +1,26 @@ + + + +

    This metric measures the number of modules that are directly imported by each module (file). +Modules that import many other modules often have too many responsibilities and are not well-focused. +This makes it difficult to understand and maintain the module. +

    + +
    + +

    Split and/or refactor files with too many responsibilities to create modules with a single, +well-defined role.

    + + +
    + + +
  • Python Language Reference: The import statement. +
  • M. Fowler, Refactoring. Addison-Wesley, 1999.
  • +
  • Wikipedia: Code refactoring.
  • + + +
    +
    diff --git a/python/ql/src/Metrics/DirectImports.ql b/python/ql/src/Metrics/DirectImports.ql new file mode 100644 index 000000000000..1eeb7694879b --- /dev/null +++ b/python/ql/src/Metrics/DirectImports.ql @@ -0,0 +1,16 @@ +/** + * @name Direct imports per file + * @description The number of modules directly imported by this file. + * @kind treemap + * @id py/direct-imports-per-file + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg max + * @tags modularity + * maintainability + */ +import python + +from ModuleObject m, int n +where n = count(ModuleObject imp | imp = m.getAnImportedModule()) +select m.getModule(), n \ No newline at end of file diff --git a/python/ql/src/Metrics/DocStringRatio.qhelp b/python/ql/src/Metrics/DocStringRatio.qhelp new file mode 100644 index 000000000000..4e124223d158 --- /dev/null +++ b/python/ql/src/Metrics/DocStringRatio.qhelp @@ -0,0 +1,35 @@ + + + +

    This metric measures the percentage of lines in a file that contain a docstring. Note that this +metric ignores comments. + +

    Docstrings are a good way to associate documentation with a specific object in Python. For public +modules, functions, classes and methods docstrings are the preferred method of documentation because +the information can be inspected by the program at runtime, for example, as an interactive help system +or as metadata for a function.

    + +

    Having a low percentage of docstrings is often an indication that a file has insufficient +documentation. However, the value for the related metric "Percentage of comments" should also be +considered because packages and non-public methods may be documented using comments. Undocumented +code is difficult to understand, modify, and reuse.

    + +
    + +

    Add documentation to files with a low docstring ratio. It is most useful to start documenting +the public functions first.

    + +
    + + +
  • Python for Beginners: +Python Docstrings.
  • +
  • Python PEP 8: Documentation +Strings.
  • +
  • Python PEP 257: Docstring Conventions.
  • + + +
    +
    diff --git a/python/ql/src/Metrics/DocStringRatio.ql b/python/ql/src/Metrics/DocStringRatio.ql new file mode 100644 index 000000000000..43d8d7af248c --- /dev/null +++ b/python/ql/src/Metrics/DocStringRatio.ql @@ -0,0 +1,17 @@ +/** + * @name Percentage of docstrings + * @description The percentage of lines in a file that contain docstrings. + * @kind treemap + * @id py/doc-string-ratio-per-file + * @treemap.warnOn lowValues + * @metricType file + * @metricAggregate avg max + * @tags maintainability + * documentation + */ +import python + +from Module m, ModuleMetrics mm +where mm = m.getMetrics() and mm.getNumberOfLines() > 0 +select m, 100.0 * ((float)mm.getNumberOfLinesOfDocStrings() / (float)mm.getNumberOfLines()) as ratio +order by ratio desc diff --git a/python/ql/src/Metrics/DuplicationProblems.qhelp b/python/ql/src/Metrics/DuplicationProblems.qhelp new file mode 100644 index 000000000000..e55f8f8e4557 --- /dev/null +++ b/python/ql/src/Metrics/DuplicationProblems.qhelp @@ -0,0 +1,17 @@ + + + +

    +Duplicated code increases overall code size, making the code base +harder to maintain and harder to understand. It also becomes harder to fix bugs, +since a programmer applying a fix to one copy has to always remember to update +other copies accordingly. Finally, code duplication is generally an indication of +a poorly designed or hastily written code base, which typically suffers from other +problems as well. +

    + + +
    +
    diff --git a/python/ql/src/Metrics/External/CommitDisplayStrings.ql b/python/ql/src/Metrics/External/CommitDisplayStrings.ql new file mode 100644 index 000000000000..dd5104996d05 --- /dev/null +++ b/python/ql/src/Metrics/External/CommitDisplayStrings.ql @@ -0,0 +1,10 @@ +/** + * @name Display strings of commits + * @kind display-string + * @id py/commit-display-strings + * @metricType commit + */ +import python +import external.VCS +from Commit c +select c.getRevisionName(), c.getMessage() + "(" + c.getDate().toString() + ")" diff --git a/python/ql/src/Metrics/External/CommitSourceLinks.ql b/python/ql/src/Metrics/External/CommitSourceLinks.ql new file mode 100644 index 000000000000..a31b73e2a7c2 --- /dev/null +++ b/python/ql/src/Metrics/External/CommitSourceLinks.ql @@ -0,0 +1,11 @@ +/** + * @name Source links of commits + * @kind source-link + * @id py/commit-source-links + * @metricType commit + */ +import python +import external.VCS +from Commit c, File f +where f.fromSource() and f = c.getAnAffectedFile() +select c.getRevisionName(), f diff --git a/python/ql/src/Metrics/FClasses.qhelp b/python/ql/src/Metrics/FClasses.qhelp new file mode 100644 index 000000000000..2584ef06b5d9 --- /dev/null +++ b/python/ql/src/Metrics/FClasses.qhelp @@ -0,0 +1,41 @@ + + + +

    This metric measures the number of classes in each file.

    + +

    There are advantages and disadvantages associated with defining multiple classes in the same file. +However, if you define unrelated classes in one file then the resulting module API is difficult for +other developers to understand and use.

    + +

    The disadvantages of putting multiple classes in the same file include:

    +
    • unless the classes are closely related, it can be difficult to understand and maintain the code, +even with good support from development tools
    • +
    • it increases the risk that multiple developers will work on the same file at once, and increases the +incidence of merge conflicts
    • +
    • it may be a symptom of badly designed modules, where many different features are handled by a +single file.
    • +
    + +

    Sometimes there are advantages of putting multiple classes in the same file, for example:

    +
    • it reduces the proliferation of files containing very few lines of code
    • +
    • it can be used to group logically-related classes together.
    + +
    + +

    Each module should have a single, well-defined role. Consequently, only logically-related classes +should be grouped together in the same file. If your code defines unrelated classes in the same file +then you should refactor the code and create new files, each containing logically related classes.

    + +
    + + +
  • Python: Class +Definitions.
  • +
  • M. Fowler, Refactoring. Addison-Wesley, 1999.
  • +
  • Wikipedia: Code refactoring.
  • + + +
    +
    diff --git a/python/ql/src/Metrics/FClasses.ql b/python/ql/src/Metrics/FClasses.ql new file mode 100644 index 000000000000..da667bd1df59 --- /dev/null +++ b/python/ql/src/Metrics/FClasses.ql @@ -0,0 +1,17 @@ +/** + * @name Classes per file + * @description Measures the number of classes in a file + * @kind treemap + * @id py/classes-per-file + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @tags maintainability + */ + +import python + +from Module m, int n +where n = count(Class c | c.getEnclosingModule() = m) +select m, n +order by n desc diff --git a/python/ql/src/Metrics/FFunctionsAndMethods.qhelp b/python/ql/src/Metrics/FFunctionsAndMethods.qhelp new file mode 100644 index 000000000000..67bd594f3b89 --- /dev/null +++ b/python/ql/src/Metrics/FFunctionsAndMethods.qhelp @@ -0,0 +1,27 @@ + + + +

    This metric measures the number of functions and methods in each file.

    + +

    Tracking this metric over time will indicate which parts of the system are under active development. +Cross-referencing with the other metrics "Cyclomatic Complexity" and "Lines of Code" is recommended, +because files with high values for all three metrics are very likely to be too big and unwieldy; such +files should be split up.

    + +
    + +

    If a file is too big, identify the different tasks that are carried out by its functions and split +the file according to these tasks.

    + +
    + + +
  • Python: Function Definitions.
  • +
  • M. Fowler, Refactoring. Addison-Wesley, 1999.
  • +
  • Wikipedia: Code refactoring.
  • + + +
    +
    diff --git a/python/ql/src/Metrics/FFunctionsAndMethods.ql b/python/ql/src/Metrics/FFunctionsAndMethods.ql new file mode 100644 index 000000000000..b8d3a43b1dde --- /dev/null +++ b/python/ql/src/Metrics/FFunctionsAndMethods.ql @@ -0,0 +1,17 @@ +/** + * @name Functions and methods per file + * @description Measures the number of functions and methods in a file. + * @kind treemap + * @id py/functions-and-methods-per-file + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @tags maintainability + */ + +import python + +from Module m, int n +where n = count(Function f | f.getEnclosingModule() = m and f.getName() != "lambda") +select m, n +order by n desc diff --git a/python/ql/src/Metrics/FLines.ql b/python/ql/src/Metrics/FLines.ql new file mode 100644 index 000000000000..04d9abad7e45 --- /dev/null +++ b/python/ql/src/Metrics/FLines.ql @@ -0,0 +1,15 @@ +/** + * @name Number of lines + * @description The number of lines in each file. + * @kind treemap + * @id py/lines-per-file + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + */ +import python + +from Module m, int n +where n = m.getMetrics().getNumberOfLines() +select m, n +order by n desc diff --git a/python/ql/src/Metrics/FLinesOfCode.qhelp b/python/ql/src/Metrics/FLinesOfCode.qhelp new file mode 100644 index 000000000000..79aaea2cfb1a --- /dev/null +++ b/python/ql/src/Metrics/FLinesOfCode.qhelp @@ -0,0 +1,37 @@ + + + +

    This metric measures the number of lines of code in each file. The value excludes docstrings, comments and +blank lines.

    + +

    Organizing source into very large files is not recommended because:

    +
    • it can be difficult to understand and maintain the code, even with good support from +development tools
    • +
    • it increases the risk that multiple developers will work on the same file at once, and increases the +incidence of merge conflicts
    • +
    • it may be a symptom of weak code organization, where many different features are handled by functions in +a single file.
    • +
    + +
    + + +

    The solution depends on the underlying cause:

    +
    • if individual classes or functions are too large then they should be refactored into smaller +modules
    • +
    • if the class contains many classes or functions, they should be +moved to their own modules (sometimes in a subsidiary module, where appropriate)
    • +
    • if the file has been automatically generated by a tool, then it should be left alone.
    • +
    + +
    + + +
  • M. Fowler, Refactoring. Addison-Wesley, 1999.
  • +
  • Wikipedia: Code refactoring.
  • + + +
    +
    diff --git a/python/ql/src/Metrics/FLinesOfCode.ql b/python/ql/src/Metrics/FLinesOfCode.ql new file mode 100644 index 000000000000..778897c6ae09 --- /dev/null +++ b/python/ql/src/Metrics/FLinesOfCode.ql @@ -0,0 +1,18 @@ +/** + * @name Lines of code in files + * @kind treemap + * @description Measures the number of lines of code in each file (ignoring lines that + * contain only docstrings, comments or are blank). + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @precision very-high + * @tags maintainability + * @id py/lines-of-code-in-files + */ +import python + +from Module m, int n +where n = m.getMetrics().getNumberOfLinesOfCode() +select m, n +order by n desc diff --git a/python/ql/src/Metrics/FLinesOfComments.qhelp b/python/ql/src/Metrics/FLinesOfComments.qhelp new file mode 100644 index 000000000000..fe91fa3d460a --- /dev/null +++ b/python/ql/src/Metrics/FLinesOfComments.qhelp @@ -0,0 +1,19 @@ + + + +

    This metric measures the number of comment lines per file. A low number of comments may indicate files that are difficult to understand due to poor documentation.

    + +
    + +

    Consider if the file needs more documentation. Most files should have at least a comment explaining their purpose.

    + +
    + + +
  • Jeff Atwood. Avoiding Undocumentation. 2005.
  • +
  • Steve McConnell. Code Complete. 2nd Edition. Microsoft Press. 2004.
  • + +
    +
    diff --git a/python/ql/src/Metrics/FLinesOfComments.ql b/python/ql/src/Metrics/FLinesOfComments.ql new file mode 100644 index 000000000000..38b19c2dc46b --- /dev/null +++ b/python/ql/src/Metrics/FLinesOfComments.ql @@ -0,0 +1,17 @@ +/** + * @name Lines of comments in files + * @kind treemap + * @description Measures the number of lines of comments in each file (including docstrings, + * and ignoring lines that contain only code or are blank). + * @treemap.warnOn lowValues + * @metricType file + * @metricAggregate avg sum max + * @precision very-high + * @id py/lines-of-comments-in-files + */ +import python + +from Module m, int n +where n = m.getMetrics().getNumberOfLinesOfComments() + m.getMetrics().getNumberOfLinesOfDocStrings() +select m, n +order by n desc diff --git a/python/ql/src/Metrics/FLinesOfDuplicatedCode.qhelp b/python/ql/src/Metrics/FLinesOfDuplicatedCode.qhelp new file mode 100644 index 000000000000..30a98df0ceeb --- /dev/null +++ b/python/ql/src/Metrics/FLinesOfDuplicatedCode.qhelp @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/python/ql/src/Metrics/FLinesOfDuplicatedCode.ql b/python/ql/src/Metrics/FLinesOfDuplicatedCode.ql new file mode 100644 index 000000000000..ac8e0a3a25cd --- /dev/null +++ b/python/ql/src/Metrics/FLinesOfDuplicatedCode.ql @@ -0,0 +1,26 @@ +/** + * @name Duplicated lines in files + * @description The number of lines in a file, including code, comment and whitespace lines, + * which are duplicated in at least one other place. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @precision high + * @tags testability + * @id py/duplicated-lines-in-files + */ +import python +import external.CodeDuplication + +from File f, int n + +where n = count(int line | + exists(DuplicateBlock d | d.sourceFile() = f | + line in [d.sourceStartLine()..d.sourceEndLine()] and + not whitelistedLineForDuplication(f, line) + ) +) + +select f, n +order by n desc diff --git a/python/ql/src/Metrics/FLinesOfSimilarCode.qhelp b/python/ql/src/Metrics/FLinesOfSimilarCode.qhelp new file mode 100644 index 000000000000..fd3aeb3bc0b3 --- /dev/null +++ b/python/ql/src/Metrics/FLinesOfSimilarCode.qhelp @@ -0,0 +1,31 @@ + + + +

    +A file that contains many lines that are similar to other code within the code base is +problematic for the same reasons as a file that contains a lot of (exactly) +duplicated code. +

    + +
    + + + + +

    +Refactor similar code snippets by extracting common functionality into functions +that can be reused across modules. +

    + +
    + + + +
  • Wikipedia: Duplicate code.
  • +
  • M. Fowler, Refactoring. Addison-Wesley, 1999.
  • + + +
    +
    diff --git a/python/ql/src/Metrics/FLinesOfSimilarCode.ql b/python/ql/src/Metrics/FLinesOfSimilarCode.ql new file mode 100644 index 000000000000..e78fe52959b6 --- /dev/null +++ b/python/ql/src/Metrics/FLinesOfSimilarCode.ql @@ -0,0 +1,26 @@ +/** + * @name Similar lines in files + * @description The number of lines in a file, including code, comment and whitespace lines, + * which are similar in at least one other place. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @precision high + * @tags testability + * @id py/similar-lines-in-files + */ +import python +import external.CodeDuplication + +from File f, int n + +where n = count(int line | + exists(SimilarBlock d | d.sourceFile() = f | + line in [d.sourceStartLine()..d.sourceEndLine()] and + not whitelistedLineForDuplication(f, line) + ) +) + +select f, n +order by n desc diff --git a/python/ql/src/Metrics/FNumberOfTests.qhelp b/python/ql/src/Metrics/FNumberOfTests.qhelp new file mode 100644 index 000000000000..be632542b741 --- /dev/null +++ b/python/ql/src/Metrics/FNumberOfTests.qhelp @@ -0,0 +1,52 @@ + + + +

    + This metric measures the number of tests below this location in the tree. + At a file level, this would just be the number of tests in the file. +

    + +

    + A function or method is considered to be a "test" if one of the major + testing frameworks would invoke it as part of a test run. + Recognized frameworks include unittest, pytest, doctest and nose. +

    + +

    + In general, having many test cases is a good thing rather than a bad + thing. However, at the file level, tests should typically be grouped + by the functionality they relate to, which makes a file with an + exceptionally high number of tests a strong candidate for splitting + up. At a higher level, this metric makes it possible to compare the + number of tests in different components, potentially flagging + functionality that is comparatively under-tested. +

    +
    + +

    + Since it is typically not a problem to have too many tests, this + metric is usually included for the purposes of collecting + information, rather than finding problematic areas in the code. With + that in mind, it is usually a good idea to avoid an excessive number + of tests in a single file, and to maintain a broadly comparable + level of testing across components. +

    + +

    + When assessing the thoroughness of a code base's test suite, the number + of tests provides only part of the story. Test coverage + statistics allow a more detailed examination of which parts of the + code deserve improvements in this area. +

    +
    + + +
  • Python Standard Library: unitest.
  • +
  • Python Standard Library: doctest.
  • +
  • http://pytest.org.
  • +
  • Read the docs: http://nose.readthedocs.org/en/latest.
  • + +
    +
    diff --git a/python/ql/src/Metrics/FNumberOfTests.ql b/python/ql/src/Metrics/FNumberOfTests.ql new file mode 100644 index 000000000000..1cc914a0d556 --- /dev/null +++ b/python/ql/src/Metrics/FNumberOfTests.ql @@ -0,0 +1,18 @@ +/** + * @name Number of tests + * @description The number of test methods defined in a module + * @kind treemap + * @treemap.warnOn lowValues + * @metricType file + * @metricAggregate avg sum max + * @precision medium + * @precision very-high + * @id py/tests-in-files + */ +import python +import semmle.python.filters.Tests + +from Module m, int n +where n = strictcount(Test test | test.getEnclosingModule() = m) +select m.getFile(), n +order by n desc diff --git a/python/ql/src/Metrics/FunctionNumberOfCalls.qhelp b/python/ql/src/Metrics/FunctionNumberOfCalls.qhelp new file mode 100644 index 000000000000..a348ddf92ba6 --- /dev/null +++ b/python/ql/src/Metrics/FunctionNumberOfCalls.qhelp @@ -0,0 +1,71 @@ + + + +

    If the number of calls that is made by a function (or method) to other functions is high, +the function can be difficult to +understand, because you have to read through all the functions that it calls +to fully understand what it does. There are various reasons why +a function may make a high number of calls, including: +

    + +
      +
    • +The function is simply too large in general. +
    • + +
    • +The function has too many responsibilities (see [Martin]). +
    • + +
    • +The function spends all of its time delegating rather than doing any work itself. +
    • +
    + +
    + + +

    +The appropriate action depends on the reason why the function +makes a high number of calls: +

    + +
      +
    • +If the function is too large, you should refactor it into multiple smaller +functions, using the 'Extract Method' refactoring from [Fowler], for example. +
    • + +
    • +If the function is taking on too many responsibilities, a new layer of functions +can be introduced below the top-level function, each of which can do some of the +original work. The top-level function then only needs to delegate to a much +smaller number of functions, which themselves delegate to the functions lower down. +
    • + +
    • +If the function spends all of its time delegating, some of the work that is done by the +subsidiary functions can be moved into the top-level function, and the subsidiary +functions can be removed. This is the refactoring called 'Inline +Method' in [Fowler]. +
    • +
    + + + +
    + + + +
  • +M. Fowler, Refactoring. Addison-Wesley, 1999. +
  • +
  • +Wikipedia: The Single Responsibility Principle. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/FunctionNumberOfCalls.ql b/python/ql/src/Metrics/FunctionNumberOfCalls.ql new file mode 100644 index 000000000000..0dd5050214ad --- /dev/null +++ b/python/ql/src/Metrics/FunctionNumberOfCalls.ql @@ -0,0 +1,16 @@ +/** + * @name Number of calls + * @description The total number of calls in a function. + * @kind treemap + * @id py/number-of-calls-per-function + * @treemap.warnOn highValues + * @metricType callable + * @metricAggregate avg max + */ + +import python + + +from FunctionMetrics func +select func, func.getNumberOfCalls() as n +order by n desc diff --git a/python/ql/src/Metrics/FunctionStatementNestingDepth.py b/python/ql/src/Metrics/FunctionStatementNestingDepth.py new file mode 100644 index 000000000000..a66e8621297c --- /dev/null +++ b/python/ql/src/Metrics/FunctionStatementNestingDepth.py @@ -0,0 +1,6 @@ +def print_character_codes_bad(strings): + if strings is not None: + for s in strings: + if s is not None: + for c in s: + print(c + '=' + ord(c)) \ No newline at end of file diff --git a/python/ql/src/Metrics/FunctionStatementNestingDepth.qhelp b/python/ql/src/Metrics/FunctionStatementNestingDepth.qhelp new file mode 100644 index 000000000000..3017a6fa018a --- /dev/null +++ b/python/ql/src/Metrics/FunctionStatementNestingDepth.qhelp @@ -0,0 +1,78 @@ + + + +

    +A method that contains a high level of nesting can be very difficult to understand. As noted in +[McConnell], the human brain cannot easily handle more than three levels of nested if +statements.

    + +
    + + +

    +Extract the control flow into a separate generator and use that to control iteration.

    + +

    +Use early exits to move nested statements out of conditions. For example: + +def func(x): + if x: + long_complex_block() + +can be replaced by + +def func(x): + if x: + return + long_complex_block() + +

    + +

    +Extract nested statements into new functions, for example by using the 'Extract Method' refactoring +from [Fowler].

    + +

    +For more ways to reduce the level of nesting in a method, see [McConnell]. +

    + +

    +Furthermore, a method that has a high level of nesting often indicates that its design can be +improved in other ways, as well as dealing with the nesting problem itself. +

    + +
    + + +

    +In the following example, the code has four levels of nesting and is unnecessarily difficult to read. +

    + + + +

    +In the following modified example, three different approaches to reducing the nesting depth are shown. +The first, print_character_codes_early_exit, uses early exits, either return +or continue. +The second, print_character_codes_use_gen, extracts the control flow into a generator. +The third, print_character_codes_extracted, uses a separate function for the inner loop. +

    + + + +
    + + + +
  • +M. Fowler, Refactoring, pp. 89-95. Addison-Wesley, 1999. +
  • +
  • +S. McConnell, Code Complete, 2nd Edition, §19.4. Microsoft Press, 2004. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/FunctionStatementNestingDepth.ql b/python/ql/src/Metrics/FunctionStatementNestingDepth.ql new file mode 100644 index 000000000000..64a72fbd34d7 --- /dev/null +++ b/python/ql/src/Metrics/FunctionStatementNestingDepth.ql @@ -0,0 +1,18 @@ +/** + * @name Statement nesting depth + * @description The maximum nesting depth of statements in a function. + * @kind treemap + * @id py/statement-nesting-depth-per-function + * @treemap.warnOn highValues + * @metricType callable + * @metricAggregate avg max + * @tags maintainability + * complexity + */ + +import python + + +from FunctionMetrics func +select func, func.getStatementNestingDepth() as n +order by n desc diff --git a/python/ql/src/Metrics/FunctionStatementNestingDepthGood.py b/python/ql/src/Metrics/FunctionStatementNestingDepthGood.py new file mode 100644 index 000000000000..1f6a651343c1 --- /dev/null +++ b/python/ql/src/Metrics/FunctionStatementNestingDepthGood.py @@ -0,0 +1,36 @@ + +# Flatten nesting by using early exits +def print_character_codes_early_exit(strings): + if strings is None: + return + for s in strings: + if s is None: + continue + for c in s: + print(c + '=' + ord(c)) + + +#Move flow control into its own generator function +def print_character_codes_use_gen(strings): + for c in gen_chars_in_strings(strings): + print(c + '=' + ord(c)) + +def gen_chars_in_strings(strings): + if strings is None: + return + for s in strings: + if s is None: + continue + for c in s: + yield c + +#Move inner loop into its own function +def print_character_codes_in_string(string): + if string is not None: + for c in string: + print(c + '=' + ord(c)) + +def print_character_codes_extracted(strings): + if strings is not None: + for s in strings: + print_character_codes_in_string(s) \ No newline at end of file diff --git a/python/ql/src/Metrics/History/HChurn.qhelp b/python/ql/src/Metrics/History/HChurn.qhelp new file mode 100644 index 000000000000..2d248a0ac182 --- /dev/null +++ b/python/ql/src/Metrics/History/HChurn.qhelp @@ -0,0 +1,42 @@ + + + +

    +This metric measures the number of lines of text that have been added, deleted +or modified in files below this location in the tree. +

    + +

    +Code churn is known to be a good (if not the best) predictor of defects in a +code component (see e.g. [Nagappan] or [Khoshgoftaar]). The intuition is that +files, packages or projects that have experienced a disproportionately high +amount of churn for the amount of code involved may have been harder to write, +and are thus likely to contain more bugs. +

    + +
    + + +

    +It is a fact of life that some code is going to be changed more than the rest, +and little can be done to change this. However, bearing in mind code churn's +effectiveness as a defect predictor, code that has been repeatedly changed +should be subjected to vigorous testing and code review. +

    + +
    + + + +
  • +N. Nagappan et al. Change Bursts as Defect Predictors. In Proceedings of the 21st IEEE International Symposium on Software Reliability Engineering, 2010. +
  • +
  • +T. M. Khoshgoftaar and R. M. Szabo. Improving code churn predictions during the system test and maintenance phases. In ICSM '94, 1994, pp. 58-67. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/History/HChurn.ql b/python/ql/src/Metrics/History/HChurn.ql new file mode 100644 index 000000000000..437fae7460c8 --- /dev/null +++ b/python/ql/src/Metrics/History/HChurn.ql @@ -0,0 +1,17 @@ +/** + * @name Churned lines per file + * @description Number of churned lines per file, across the revision history in the database. + * @kind treemap + * @id py/historical-churn + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + */ +import python +import external.VCS + +from Module m, int n +where n = sum(Commit entry, int churn | churn = entry.getRecentChurnForFile(m.getFile()) and not artificialChange(entry) | churn) + and exists(m.getMetrics().getNumberOfLinesOfCode()) +select m, n +order by n desc diff --git a/python/ql/src/Metrics/History/HLinesAdded.qhelp b/python/ql/src/Metrics/History/HLinesAdded.qhelp new file mode 100644 index 000000000000..fc812fc8357c --- /dev/null +++ b/python/ql/src/Metrics/History/HLinesAdded.qhelp @@ -0,0 +1,6 @@ + + + + diff --git a/python/ql/src/Metrics/History/HLinesAdded.ql b/python/ql/src/Metrics/History/HLinesAdded.ql new file mode 100644 index 000000000000..9eea86871184 --- /dev/null +++ b/python/ql/src/Metrics/History/HLinesAdded.ql @@ -0,0 +1,17 @@ +/** + * @name Added lines per file + * @description Number of added lines per file, across the revision history in the database. + * @kind treemap + * @id py/historical-lines-added + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + */ +import python +import external.VCS + +from Module m, int n +where n = sum(Commit entry, int churn | churn = entry.getRecentAdditionsForFile(m.getFile()) and not artificialChange(entry) | churn) + and exists(m.getMetrics().getNumberOfLinesOfCode()) +select m, n +order by n desc diff --git a/python/ql/src/Metrics/History/HLinesDeleted.qhelp b/python/ql/src/Metrics/History/HLinesDeleted.qhelp new file mode 100644 index 000000000000..fc812fc8357c --- /dev/null +++ b/python/ql/src/Metrics/History/HLinesDeleted.qhelp @@ -0,0 +1,6 @@ + + + + diff --git a/python/ql/src/Metrics/History/HLinesDeleted.ql b/python/ql/src/Metrics/History/HLinesDeleted.ql new file mode 100644 index 000000000000..905d15b524c1 --- /dev/null +++ b/python/ql/src/Metrics/History/HLinesDeleted.ql @@ -0,0 +1,17 @@ +/** + * @name Deleted lines per file + * @description Number of deleted lines per file, across the revision history in the database. + * @kind treemap + * @id py/historical-lines-deleted + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + */ +import python +import external.VCS + +from Module m, int n +where n = sum(Commit entry, int churn | churn = entry.getRecentDeletionsForFile(m.getFile()) and not artificialChange(entry) | churn) + and exists(m.getMetrics().getNumberOfLinesOfCode()) +select m, n +order by n desc diff --git a/python/ql/src/Metrics/History/HNumberOfAuthors.qhelp b/python/ql/src/Metrics/History/HNumberOfAuthors.qhelp new file mode 100644 index 000000000000..00ec48b744f0 --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfAuthors.qhelp @@ -0,0 +1,48 @@ + + + +

    +This metric measures the number of different authors (by examining the +version control history) +for files below this location in the tree. (This is a better version +of the metric that counts the number of different authors using Javadoc +tags.) +

    + +

    +Files that have been changed by a large number of different authors are +by definition the product of many minds. New authors working on a file +may be less familiar with the design and implementation of the code than +the original authors, which can be a potential source of bugs. Furthermore, +code that has been worked on by many people, if not carefully maintained, +often ends up lacking conceptual integrity. For both of these reasons, any +code that has been worked on by an unusually high number of different people +merits careful inspection in code reviews. +

    + +
    + + +

    +There is clearly no way to reduce the number of authors that have worked +on a file - it is impossible to rewrite history. However, files highlighted +by this metric should be given special attention in a code review, and may +ultimately be good candidates for refactoring/rewriting by an individual, +experienced developer. +

    + + + +
    + + + +
  • +F. P. Brooks Jr. The Mythical Man-Month, Chapter 4. Addison-Wesley, 1974. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/History/HNumberOfAuthors.ql b/python/ql/src/Metrics/History/HNumberOfAuthors.ql new file mode 100644 index 000000000000..fef769fc705d --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfAuthors.ql @@ -0,0 +1,16 @@ +/** + * @name Number of authors + * @description Number of distinct authors for each file + * @kind treemap + * @id py/historical-number-of-authors + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg min max + */ +import python +import external.VCS + +from Module m +where exists(m.getMetrics().getNumberOfLinesOfCode()) +select m, count(Author author | author.getAnEditedFile() = m.getFile()) + diff --git a/python/ql/src/Metrics/History/HNumberOfCoCommits.qhelp b/python/ql/src/Metrics/History/HNumberOfCoCommits.qhelp new file mode 100644 index 000000000000..6a767d576589 --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfCoCommits.qhelp @@ -0,0 +1,51 @@ + + + +

    +This metric measures the average number of co-committed files for the files +below this location in the tree. +

    + +

    +A co-committed file is one that is committed at the same time as a given file. +For instance, if you commit files A, B and C together, then B and C would be +the co-committed files of A for that commit. The value of the metric for an +individual file is the average number of such co-committed files over all +commits. The value of the metric for a directory is the aggregation of these +averages - for instance, if we are using max as our aggregation +function, the value would be the maximum of the average number of co-commits +over all files in the directory. +

    + +

    +An unusually high value for this metric may indicate that the file in question +is too tightly-coupled to other files, and it is difficult to change it in +isolation. Alternatively, it may just be an indication that you commit lots of +unrelated changes at the same time. +

    + +
    + + +

    +Examine the file in question to see what the problem is. +

    + +
      +
    • +If the file is too tightly coupled, it will have high values for its afferent +and/or efferent coupling metrics, and you should apply the advice given there. +
    • + +
    • +If the file is not tightly coupled, but you find that you are committing lots +of unrelated changes at the same time, then you may want to revisit your commit +practices. +
    • +
    + + +
    +
    diff --git a/python/ql/src/Metrics/History/HNumberOfCoCommits.ql b/python/ql/src/Metrics/History/HNumberOfCoCommits.ql new file mode 100644 index 000000000000..81dbe8ba2da6 --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfCoCommits.ql @@ -0,0 +1,20 @@ +/** + * @name Number of co-committed files + * @description The average number of other files that are touched whenever a file is affected by a commit + * @kind treemap + * @id py/historical-number-of-co-commits + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg min max + */ +import python +import external.VCS + +int committedFiles(Commit commit) { + result = count(commit.getAnAffectedFile()) +} + +from Module m +where exists(m.getMetrics().getNumberOfLinesOfCode()) +select m, avg(Commit commit, int toAvg | (commit.getAnAffectedFile() = m.getFile()) and (toAvg = committedFiles(commit)-1) | toAvg) + diff --git a/python/ql/src/Metrics/History/HNumberOfCommits.qhelp b/python/ql/src/Metrics/History/HNumberOfCommits.qhelp new file mode 100644 index 000000000000..e54825e484d9 --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfCommits.qhelp @@ -0,0 +1,15 @@ + + + +

    +This metric measures the total number of commits made to files +below this location in the tree. For an individual file, it measures the +number of commits that have affected that file. For a directory of files, it +measures the total number of commits affecting files below that +directory. +

    + +
    +
    diff --git a/python/ql/src/Metrics/History/HNumberOfCommits.ql b/python/ql/src/Metrics/History/HNumberOfCommits.ql new file mode 100644 index 000000000000..deca31e1444e --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfCommits.ql @@ -0,0 +1,15 @@ +/** + * @name Number of commits + * @description Number of commits + * @kind treemap + * @id py/historical-number-of-commits + * @treemap.warnOn highValues + * @metricType commit + * @metricAggregate sum + */ +import python +import external.VCS + +from Commit c +where not artificialChange(c) +select c.getRevisionName(), 1 diff --git a/python/ql/src/Metrics/History/HNumberOfReCommits.qhelp b/python/ql/src/Metrics/History/HNumberOfReCommits.qhelp new file mode 100644 index 000000000000..37edc9aecf15 --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfReCommits.qhelp @@ -0,0 +1,53 @@ + + + +

    +This metric measures the number of file re-commits that have occurred below +this location in the tree. A re-commit is taken to mean a commit to a file +that was touched less than five days ago. +

    + +

    +In a system that is being developed using a controlled change process (where +changes are not committed until they are in some sense 'complete'), re-commits +can be (but are not always) an indication that an initial change was not +successful and had to be revisited within a short time period. The intuition +is that the original change may have been difficult to get right, and hence +the code in the file may be more than usually defect-prone. The concept is +somewhat similar to that of 'change bursts', as described in [Nagappan]. +

    + +
    + + +

    +High numbers of re-commits can be addressed on two levels: preventative and +corrective. +

    + +
      +
    • +On the preventative side, a high number of re-commits may be an indication +that your code review process needs an overhaul. +
    • + +
    • +On the corrective side, code that has experienced a high number of re-commits +should be vigorously code reviewed and tested. +
    • +
    + + +
    + + + +
  • +N. Nagappan et al. Change Bursts as Defect Predictors. In Proceedings of the 21st IEEE International Symposium on Software Reliability Engineering, 2010. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/History/HNumberOfReCommits.ql b/python/ql/src/Metrics/History/HNumberOfReCommits.ql new file mode 100644 index 000000000000..f5831944aed3 --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfReCommits.ql @@ -0,0 +1,29 @@ +/** + * @name Number of re-commits for each file + * @description A re-commit is taken to mean a commit to a file that was touched less than five days ago. + * @kind treemap + * @id py/historical-number-of-re-commits + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg min max + */ +import python +import external.VCS + +predicate inRange(Commit first, Commit second) { + first.getAnAffectedFile() = second.getAnAffectedFile() and + first != second and + exists(int n | n = first.getDate().daysTo(second.getDate()) and + n >= 0 and n < 5) +} + +int recommitsForFile(File f) { + result = count(Commit recommit | + f = recommit.getAnAffectedFile() and + exists(Commit prev | inRange(prev, recommit))) +} + +from Module m +where exists(m.getMetrics().getNumberOfLinesOfCode()) +select m, recommitsForFile(m.getFile()) + diff --git a/python/ql/src/Metrics/History/HNumberOfRecentAuthors.ql b/python/ql/src/Metrics/History/HNumberOfRecentAuthors.ql new file mode 100644 index 000000000000..6ea84550f76c --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfRecentAuthors.ql @@ -0,0 +1,16 @@ +/** + * @name Number of recent authors + * @description Number of distinct authors that have recently made changes + * @kind treemap + * @id py/historical-number-of-recent-authors + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg min max + */ +import python +import external.VCS + +from Module m +where exists(m.getMetrics().getNumberOfLinesOfCode()) +select m, count(Author author | exists(Commit e | e = author.getACommit() and m.getFile() = e.getAnAffectedFile() and e.daysToNow() <= 180 and not artificialChange(e))) + diff --git a/python/ql/src/Metrics/History/HNumberOfRecentChangedFiles.ql b/python/ql/src/Metrics/History/HNumberOfRecentChangedFiles.ql new file mode 100644 index 000000000000..3f35a9cba77d --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfRecentChangedFiles.ql @@ -0,0 +1,17 @@ +/** + * @name Recently changed files + * @description Number of files recently edited + * @kind treemap + * @id py/historical-number-of-recent-changed-files + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg min max + */ +import python +import external.VCS + +from Module m +where exists(Commit e | e.getAnAffectedFile() = m.getFile() and e.daysToNow() <= 180 and not artificialChange(e)) + and exists(m.getMetrics().getNumberOfLinesOfCode()) +select m, 1 + diff --git a/python/ql/src/Metrics/History/HNumberOfRecentCommits.qhelp b/python/ql/src/Metrics/History/HNumberOfRecentCommits.qhelp new file mode 100644 index 000000000000..4860add6342b --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfRecentCommits.qhelp @@ -0,0 +1,63 @@ + + + +

    +This metric measures the number of recent commits to files that have occurred +below this location in the tree. A recent commits is taken to mean a commits +that has occurred in the last 180 days. +

    + +

    +All code that has changed a great deal may be more than usually prone to +defects, but this is particularly true of code that has been changing +dramatically in the recent past, because it has not yet had a chance to be +properly field-tested in order to iron out the bugs. +

    + +
    + + +

    +There is more than one reason why a file may have been changing a lot +recently: +

    + +
      +
    • +The file may be part of a new subsystem that is being written. New code is +always going to change a lot in a short period of time, but it is important +to ensure that it is properly code reviewed and unit tested before integrating +it into a working product. +
    • + +
    • +The file may be being heavily refactored. Large refactorings are sometimes +essential, but they are also quite risky. You should write proper regression +tests before starting on a major refactoring, and check that they still pass +once you're done. +
    • + +
    • +The same bit of code may be being changed repeatedly because it is difficult +to get right. Aside from vigorous code reviewing and testing, it may be a good +idea to rethink the system design - if something is that hard +to get right (and it's not an inherently difficult concept), you might be making life unnecessarily hard for yourself and +risking introducing insidious defects. +
    • +
    + + + +
    + + + +
  • +N. Nagappan et al. Change Bursts as Defect Predictors. In Proceedings of the 21st IEEE International Symposium on Software Reliability Engineering, 2010. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/History/HNumberOfRecentCommits.ql b/python/ql/src/Metrics/History/HNumberOfRecentCommits.ql new file mode 100644 index 000000000000..e9e3b14538f6 --- /dev/null +++ b/python/ql/src/Metrics/History/HNumberOfRecentCommits.ql @@ -0,0 +1,16 @@ +/** + * @name Recent changes + * @description Number of recent commits + * @kind treemap + * @id py/historical-number-of-recent-commits + * @treemap.warnOn highValues + * @metricType commit + * @metricAggregate sum + */ +import python +import external.VCS + +from Commit c +where c.daysToNow() <= 180 and not artificialChange(c) +select c.getRevisionName(), 1 + diff --git a/python/ql/src/Metrics/Internal/CallableDisplayStrings.ql b/python/ql/src/Metrics/Internal/CallableDisplayStrings.ql new file mode 100644 index 000000000000..47a6f20db3e7 --- /dev/null +++ b/python/ql/src/Metrics/Internal/CallableDisplayStrings.ql @@ -0,0 +1,10 @@ +/** + * @name Display strings of callables + * @kind display-string + * @id py/function-display-strings + * @metricType callable + */ +import python + +from Function f +select f, "Function " + f.getName() diff --git a/python/ql/src/Metrics/Internal/CallableExtents.ql b/python/ql/src/Metrics/Internal/CallableExtents.ql new file mode 100644 index 000000000000..7e2d0baedfa7 --- /dev/null +++ b/python/ql/src/Metrics/Internal/CallableExtents.ql @@ -0,0 +1,11 @@ +/** + * @name Extents of callables + * @kind extent + * @id py/function-extents + * @metricType callable + */ +import python +import Extents + +from RangeFunction f +select f.getLocation(), f diff --git a/python/ql/src/Metrics/Internal/CallableSourceLinks.ql b/python/ql/src/Metrics/Internal/CallableSourceLinks.ql new file mode 100644 index 000000000000..41278a186841 --- /dev/null +++ b/python/ql/src/Metrics/Internal/CallableSourceLinks.ql @@ -0,0 +1,10 @@ +/** + * @name Source links of callables + * @kind source-link + * @id py/function-source-links + * @metricType callable + */ +import python + +from Function f +select f, f.getLocation().getFile() diff --git a/python/ql/src/Metrics/Internal/ClassDisplayStrings.ql b/python/ql/src/Metrics/Internal/ClassDisplayStrings.ql new file mode 100644 index 000000000000..612abfebec78 --- /dev/null +++ b/python/ql/src/Metrics/Internal/ClassDisplayStrings.ql @@ -0,0 +1,10 @@ +/** + * @name Display strings of classes + * @kind display-string + * @id py/lgtm/class-display-strings + * @metricType reftype + */ +import python + +from Class c +select c, c.getName() diff --git a/python/ql/src/Metrics/Internal/ClassExtents.ql b/python/ql/src/Metrics/Internal/ClassExtents.ql new file mode 100644 index 000000000000..cc5fd7e9390f --- /dev/null +++ b/python/ql/src/Metrics/Internal/ClassExtents.ql @@ -0,0 +1,11 @@ +/** + * @name Extents of classes + * @kind extent + * @id py/class-extents + * @metricType reftype + */ +import python +import Extents + +from RangeClass c +select c.getLocation(), c diff --git a/python/ql/src/Metrics/Internal/ClassSourceLinks.ql b/python/ql/src/Metrics/Internal/ClassSourceLinks.ql new file mode 100644 index 000000000000..089596a0d40a --- /dev/null +++ b/python/ql/src/Metrics/Internal/ClassSourceLinks.ql @@ -0,0 +1,10 @@ +/** + * @name Source links of classes + * @kind source-link + * @id py/class-source-links + * @metricType reftype + */ +import python + +from Class c +select c, c.getLocation().getFile() diff --git a/python/ql/src/Metrics/Internal/Extents.qll b/python/ql/src/Metrics/Internal/Extents.qll new file mode 100644 index 000000000000..283f1fb7c30b --- /dev/null +++ b/python/ql/src/Metrics/Internal/Extents.qll @@ -0,0 +1,33 @@ +import python + +/* + * When this library is imported, the 'hasLocationInfo' predicate of + * Functions and is overridden to specify their entire range + * instead of just the range of their name. The latter can still be + * obtained by invoking the getLocation() predicate. + * + * The full ranges are required for the purpose of associating an alert + * with an individual Function as opposed to a whole File. + */ + +/** + * A Function whose 'hasLocationInfo' is overridden to specify its entire range + * including the body (if any), as opposed to the location of its name only. + */ +class RangeFunction extends Function { + predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + super.getLocation().hasLocationInfo(path, sl, sc, _, _) + and this.getBody().getLastItem().getLocation().hasLocationInfo(path, _, _, el, ec) + } +} + +/** + * A Class whose 'hasLocationInfo' is overridden to specify its entire range + * including the body (if any), as opposed to the location of its name only. + */ +class RangeClass extends Class { + predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + super.getLocation().hasLocationInfo(path, sl, sc, _, _) + and this.getBody().getLastItem().getLocation().hasLocationInfo(path, _, _, el, ec) + } +} \ No newline at end of file diff --git a/python/ql/src/Metrics/LackofCohesionInMethodsCK.qhelp b/python/ql/src/Metrics/LackofCohesionInMethodsCK.qhelp new file mode 100644 index 000000000000..83fc3a05dd4b --- /dev/null +++ b/python/ql/src/Metrics/LackofCohesionInMethodsCK.qhelp @@ -0,0 +1,68 @@ + + + +

    +A cohesive class is one in which most methods access the same fields. A class that +lacks cohesion is usually one that has multiple responsibilities. +

    + +

    +Various measures of lack of cohesion have been proposed. The Chidamber and Kemerer +version of lack of cohesion inspects pairs of methods. If there are many pairs that +access the same data, the class is cohesive. If there are many pairs that do not access +any common data, the class is not cohesive. More precisely, if:

    + +
      +
    • n1 is the number of pairs of distinct methods in a class that + do not have at least one commonly-accessed field, and
    • +
    • n2 is the number of pairs of distinct methods in a class that + do have at least one commonly-accessed field,
    • +
    + +

    the lack of cohesion measure (LCOM) can be defined as: +

    + +

    +LCOM = max((n1 - n2) / 2, 0) +

    + +

    +High values of LCOM indicate a significant lack of cohesion. As a rough +indication, an LCOM of 500 or more may give you cause for concern. +

    + +
    + + +

    +Classes generally lack cohesion because they have more responsibilities +than they should (see [Martin]). In general, the solution is to identify each +of the different responsibilities that the class has, and split them +into multiple classes, using the 'Extract Class' refactoring from [Fowler], for +example. +

    + + + +
    + + + +
  • +S. R. Chidamber and C. F. Kemerer, A metrics suite for object-oriented design. IEEE Transactions on Software Engineering, 20(6):476-493, 1994. +
  • +
  • +M. Fowler, Refactoring, pp. 65, 122-5. Addison-Wesley, 1999. +
  • +
  • +Wikipedia: The Single Responsibility Principle. +
  • +
  • +O. de Moor et al, Keynote Address: .QL for Source Code Analysis. Proceedings of the 7th IEEE International Working Conference on Source Code Analysis and Manipulation, 2007. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/LackofCohesionInMethodsCK.ql b/python/ql/src/Metrics/LackofCohesionInMethodsCK.ql new file mode 100644 index 000000000000..c950cd4bac4f --- /dev/null +++ b/python/ql/src/Metrics/LackofCohesionInMethodsCK.ql @@ -0,0 +1,16 @@ +/** + * @name Lack of Cohesion in Methods (CK) + * @description Lack of cohesion in the methods of a class, as defined by Chidamber and Kemerer. + * @kind treemap + * @id py/lack-of-cohesion-chidamber-kemerer + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + */ + +import python + + +from ClassMetrics cls +select cls, cls.getLackOfCohesionCK() as n +order by n desc diff --git a/python/ql/src/Metrics/LackofCohesionInMethodsHM.qhelp b/python/ql/src/Metrics/LackofCohesionInMethodsHM.qhelp new file mode 100644 index 000000000000..e2c492e67472 --- /dev/null +++ b/python/ql/src/Metrics/LackofCohesionInMethodsHM.qhelp @@ -0,0 +1,56 @@ + + + +

    +A cohesive class is one in which most methods access the same fields. A class that +lacks cohesion is usually one that has multiple responsibilities. +

    + +

    +Various measures of lack of cohesion have been proposed. A measure proposed by Hitz and Montazeri +counts the number of strongly connected components, that is disjoint subgraphs, +in the graph of method and attribute dependencies. +This can be thought of as the number of possible classes that a single class could be split into. +

    + +

    +Values of LCOM above 1 indicate a lack of cohesion in that there are several +disjoint subgraphs in a graph of intra-class dependencies. +

    + +
    + + +

    +Classes generally lack cohesion because they have more responsibilities +than they should (see [Martin]). In general, the solution is to identify each +of the different responsibilities that the class has, and split them +into multiple classes, using the 'Extract Class' refactoring from [Fowler], for +example. +

    + + + +
    + + + +
  • + + Measuring coupling and cohesion in object-oriented systems by Martin Hitz, Behzad Montazeri (1995). + Proceedings of International Symposium on Applied Corporate Computing
  • +
  • +M. Fowler, Refactoring, pp. 65, 122-5. Addison-Wesley, 1999. +
  • +
  • +Wikipedia: The Single Responsibility Principle. +
  • +
  • +Wikipedia: Strongly connected component. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/LackofCohesionInMethodsHM.ql b/python/ql/src/Metrics/LackofCohesionInMethodsHM.ql new file mode 100644 index 000000000000..0a315c44ea74 --- /dev/null +++ b/python/ql/src/Metrics/LackofCohesionInMethodsHM.ql @@ -0,0 +1,16 @@ +/** + * @name Lack of Cohesion in a Class (HM) + * @description Lack of cohesion of a class, as defined by Hitz and Montazeri. + * @kind treemap + * @id py/lack-of-cohesion-hitz-montazeri + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + */ + +import python + + +from ClassMetrics cls +select cls, cls.getLackOfCohesionHM() as n +order by n desc diff --git a/python/ql/src/Metrics/ModuleAfferentCoupling.qhelp b/python/ql/src/Metrics/ModuleAfferentCoupling.qhelp new file mode 100644 index 000000000000..2466791bd7e5 --- /dev/null +++ b/python/ql/src/Metrics/ModuleAfferentCoupling.qhelp @@ -0,0 +1,72 @@ + + + +

    +This metric measures the number of incoming dependencies for each +module, that is the number of other modules that depend on it. +

    + +

    +Modules that are depended upon by many other modules typically require a lot of +effort to change, because changing them will force their dependents to change +as well. This is not necessarily a bad thing -- indeed, most systems will have +some such modules (one example might be an I/O module). However, modules with a high number +of incoming dependencies and a high number of outgoing dependencies are hard to maintain. +A module with both high afferent coupling and high efferent coupling can be problematic +because, on the one hand they are hard to change (high afferent coupling), yet on the other they +have many reasons to change (high efferent coupling). This contradiction yields code that is very +hard to maintain or test. +

    + +

    +Conversely, some modules may only be depended on by very few other modules. Again, +this is not necessarily a problem -- we would expect, for example, that the +top-level modules of a system would meet this criterion. When lower-level +modules have very few incoming dependencies, however, it can be an indication +that a module is not pulling its weight. In extreme cases, modules may even +have an afferent coupling of 0, indicating that they are dead +code. +

    + +
    + + +

    +It is unwise to refactor a module based purely on its high or low number of +incoming dependencies -- a module's afferent coupling value only makes sense +in the context of its role in the system as a whole. However, when combined +with other metrics such as efferent coupling, it is possible to make some +general recommendations: +

    + +
      +
    • +Modules with high numbers of incoming and outgoing dependencies +are prime candidates for refactoring (although this +will not always be easy). The general strategy is to split the module into +smaller modules that each have fewer responsibilities, and refactor the code +that previously used that module accordingly. +
    • + + +
    • +Modules that have an afferent coupling of 0 may be dead code -- +in this situation, they can often be deleted. +
    • +
    + + + +
    + + + +
  • +M. Fowler. Refactoring. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/ModuleAfferentCoupling.ql b/python/ql/src/Metrics/ModuleAfferentCoupling.ql new file mode 100644 index 000000000000..f8f5e0c42085 --- /dev/null +++ b/python/ql/src/Metrics/ModuleAfferentCoupling.ql @@ -0,0 +1,18 @@ +/** + * @name Incoming module dependencies + * @description The number of modules that depend on a module. + * @kind treemap + * @id py/afferent-coupling-per-file + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg max + * @tags maintainability + * modularity + */ + +import python + +from ModuleMetrics m +select m, m.getAfferentCoupling() as n +order by n desc + diff --git a/python/ql/src/Metrics/ModuleEfferentCoupling.qhelp b/python/ql/src/Metrics/ModuleEfferentCoupling.qhelp new file mode 100644 index 000000000000..15cf254efac0 --- /dev/null +++ b/python/ql/src/Metrics/ModuleEfferentCoupling.qhelp @@ -0,0 +1,40 @@ + + + +

    +Efferent coupling is the number of outgoing dependencies for each module. In other words, it is the +number of other modules on which each module depends. +

    + +

    +A module that depends on many other modules is quite brittle, because if any of +its dependencies change, the module itself may have to change as well. Furthermore, the +reason for the high number of dependencies is often that different parts of +the module depend on different groups of other modules, so it is common to +find that modules with high efferent coupling also lack cohesion. +

    + +
    + + +

    +You can reduce efferent coupling by splitting up a module so that each part depends on fewer modules. +

    + + +
    + + + +
  • +IBM developerWorks: Evolutionary architecture and emergent design: Emergent design through metrics. +
  • +
  • +R. Martin, Agile Software Development: Principles, Patterns and Practices. Pearson, 2011. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/ModuleEfferentCoupling.ql b/python/ql/src/Metrics/ModuleEfferentCoupling.ql new file mode 100644 index 000000000000..be32b8bc561e --- /dev/null +++ b/python/ql/src/Metrics/ModuleEfferentCoupling.ql @@ -0,0 +1,18 @@ +/** + * @name Outgoing module dependencies + * @description The number of modules that this module depends upon. + * @kind treemap + * @id py/efferent-coupling-per-file + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg max + * @tags testability + * modularity + */ + +import python + +from ModuleMetrics m +select m, m.getEfferentCoupling() as n +order by n desc + diff --git a/python/ql/src/Metrics/NumberOfParameters1.py b/python/ql/src/Metrics/NumberOfParameters1.py new file mode 100644 index 000000000000..b0343da88539 --- /dev/null +++ b/python/ql/src/Metrics/NumberOfParameters1.py @@ -0,0 +1,5 @@ +def print_annotation(message, line, offset, length): + print("Message: " + message) + print("Line: " + line) + print("Offset: " + offset) + print("Length: " + length) \ No newline at end of file diff --git a/python/ql/src/Metrics/NumberOfParameters1Good.py b/python/ql/src/Metrics/NumberOfParameters1Good.py new file mode 100644 index 000000000000..6788b12e43de --- /dev/null +++ b/python/ql/src/Metrics/NumberOfParameters1Good.py @@ -0,0 +1,9 @@ +class Annotation: + #... + pass + +def print_annotation(annotation): + print("Message: " + annotation.message) + print("Line: " + annotation.line) + print("Offset: " + annotation.offset) + print("Length: " + annotation.length) diff --git a/python/ql/src/Metrics/NumberOfParameters2.py b/python/ql/src/Metrics/NumberOfParameters2.py new file mode 100644 index 000000000000..8429b98dd15c --- /dev/null +++ b/python/ql/src/Metrics/NumberOfParameters2.py @@ -0,0 +1,13 @@ +def print_membership(fellows, members, associates, students): + for f in fellows: + print(f) + for m in members: + print(m) + for a in associates: + print(a) + for s in students: + print(s) + +def print_records(): + #... + print_membership(fellows, members, associates, students) \ No newline at end of file diff --git a/python/ql/src/Metrics/NumberOfParameters2Good.py b/python/ql/src/Metrics/NumberOfParameters2Good.py new file mode 100644 index 000000000000..82300c467ea7 --- /dev/null +++ b/python/ql/src/Metrics/NumberOfParameters2Good.py @@ -0,0 +1,13 @@ + +def print_fellows(fellows): + for f in fellows: + print(f) + +#... + +def print_records(): + #... + print_fellows(fellows) + print_members(members) + print_associates(associates) + print_students(students) \ No newline at end of file diff --git a/python/ql/src/Metrics/NumberOfParametersWithoutDefault.qhelp b/python/ql/src/Metrics/NumberOfParametersWithoutDefault.qhelp new file mode 100644 index 000000000000..cfeb5fc41be3 --- /dev/null +++ b/python/ql/src/Metrics/NumberOfParametersWithoutDefault.qhelp @@ -0,0 +1,92 @@ + + + + +

    +A function (or method) that uses a high number of parameters makes maintenance more difficult: +

    + +
      +
    • It is difficult to write a call to the function, because the programmer must know how to +supply an appropriate value for each parameter.
    • + +
    • It is externally difficult to understand, because calls +to the function are longer than a single line of code.
    • + +
    • It can be internally difficult to understand, because it +has so many dependencies.
    • +
    + +
    + + +

    +Restrict the number of parameters for a function, according to the reason for the high number: +

    + +
      +
    • Several of the parameters are logically related, but are +passed into the function separately. The parameters that are logically related should be grouped together +(see the 'Introduce Parameter Object' refactoring on pp. 238-242 of [Fowler]).
    • + +
    • The function has too many responsibilities. It should be broken into multiple functions (see the +'Extract Method' refactoring on pp. 89-95 of [Fowler]), and each new function should be passed +a subset of the original parameters.
    • + +
    • The function has redundant parameters that are not used. The two main reasons for this are: +(1) parameters were added for future extensibility but are never used; (2) the body of the function was changed +so that it no longer uses certain parameters, but the function signature was not +correspondingly updated. In both cases, the theoretically correct solution is to delete the unused +parameters (see the 'Remove Parameter' refactoring on pp. 223-225 of [Fowler]), although you must do +this cautiously if the function is part of a published interface.
    • +
    + +

    When a function is part of a published interface, one possible solution is to add a new, wrapper +function to the interface that has a tidier signature. Alternatively, you can publish a new version of +the interface that has a better design. Clearly, however, neither of these solutions is ideal, +so you should take care to design interfaces the right way from the start.

    + +

    The practice of adding parameters for future extensibility is especially +bad. It is confusing to other programmers, who are uncertain what values they should pass +in for these unnecessary parameters, and it adds unused code that is potentially difficult to remove +later.

    + +
    +
    + +

    In the following example, although the parameters are logically related, they are passed into the +print_annotation function separately.

    + + + +

    In the following modified example, the print_annotation function is simplified by logically grouping +the related parameters into a single class. +An instance of the class can then be passed into the function instead, as shown below. +

    + + + +

    In the following example, the print_membership function has too many responsibilities, +and so needs to be passed four arguments.

    + + + +

    In the following modified example, print_membership has been broken into four functions. +(For brevity, only one function is shown.) As a result, each new function needs to be passed only one +of the original four arguments.

    + + + +
    + + + +
  • +M. Fowler, Refactoring. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/python/ql/src/Metrics/NumberOfParametersWithoutDefault.ql b/python/ql/src/Metrics/NumberOfParametersWithoutDefault.ql new file mode 100644 index 000000000000..4ddd2ba1f0e7 --- /dev/null +++ b/python/ql/src/Metrics/NumberOfParametersWithoutDefault.ql @@ -0,0 +1,18 @@ +/** + * @name Number of parameters without defaults + * @description The number of parameters of a function that do not have default values defined. + * @kind treemap + * @id py/number-of-parameters-without-default-per-function + * @treemap.warnOn highValues + * @metricType callable + * @metricAggregate avg max + * @tags testability + * complexity + */ + +import python + + +from FunctionMetrics func +select func, func.getNumberOfParametersWithoutDefault() as n +order by n desc diff --git a/python/ql/src/Metrics/NumberOfStatements.qhelp b/python/ql/src/Metrics/NumberOfStatements.qhelp new file mode 100644 index 000000000000..e33313c91f66 --- /dev/null +++ b/python/ql/src/Metrics/NumberOfStatements.qhelp @@ -0,0 +1,65 @@ + + + +

    +This metric measures the number of statements that occur in a module. +

    + +

    +If there are too many statements in a module, it is generally +for one of two reasons: +

    + +
      +
    • +One or more individual classes or functions of the module contain too many statements, +making them hard to understand, difficult to check and a common source of defects +(particularly towards the end of the class or function, since few people ever read that +far). These entities typically lack cohesion because they are trying to do too many things. +
    • + +
    • +The module contains too many functions or classes, which generally indicates that it is +trying to do too much, either at the interface or implementation level or +both. It can be difficult for readers to understand because there is a +confusing list of operations. +
    • +
    + +
    + + +

    +As described above, modules reported as violations by this rule contain one +or more classes or functions with too many statements, or the module itself contains +too many classes or functions.

    + +
      +
    • +Individual classes or functions of the module that contain too many statements +should be refactored into multiple, smaller parts. As a rough +guide, functions should be able to fit on a single screen or side of A4. Anything +longer than that increases the risk of introducing new defects during routine code changes. +
    • + +
    • +Modules that contain too many functions or classes often lack cohesion and are +prime candidates for refactoring. +
    • +
    + + +
    + + + +
  • +M. Fowler. Refactoring. Addison-Wesley, 1999. +
  • + + + +
    +
    diff --git a/python/ql/src/Metrics/NumberOfStatements.ql b/python/ql/src/Metrics/NumberOfStatements.ql new file mode 100644 index 000000000000..66263f68a84d --- /dev/null +++ b/python/ql/src/Metrics/NumberOfStatements.ql @@ -0,0 +1,15 @@ +/** + * @name Number of statements + * @description The number of statements in this module + * @kind treemap + * @id py/number-of-statements-per-file + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + */ +import python + +from Module m, int n +where n = count(Stmt s | s.getEnclosingModule() = m) +select m, n +order by n desc diff --git a/python/ql/src/Metrics/TransitiveImports.qhelp b/python/ql/src/Metrics/TransitiveImports.qhelp new file mode 100644 index 000000000000..7946423547f1 --- /dev/null +++ b/python/ql/src/Metrics/TransitiveImports.qhelp @@ -0,0 +1,25 @@ + + + +

    This metric measures the number of modules that are imported by each module (file) - either directly +by an import statement or indirectly (that is, imported by a module that is imported). Modules that +import many other modules often have too many responsibilities and are not well-focused. +This makes it difficult to understand and maintain the module. +

    + +
    + +

    Split and/or refactor files with too many responsibilities to create modules with a single, +well-defined role.

    + +
    + + +
  • Python Language Reference: The import statement. +
  • M. Fowler, Refactoring. Addison-Wesley, 1999.
  • +
  • Wikipedia: Code refactoring.
  • + +
    +
    diff --git a/python/ql/src/Metrics/TransitiveImports.ql b/python/ql/src/Metrics/TransitiveImports.ql new file mode 100644 index 000000000000..11fe7ee8f7e6 --- /dev/null +++ b/python/ql/src/Metrics/TransitiveImports.ql @@ -0,0 +1,16 @@ +/** + * @name Indirect imports per file + * @description The number of modules imported by this file - either directly by an import statement, + * or indirectly (by being imported by an imported module). + * @kind treemap + * @id py/transitive-imports-per-file + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg max + * @tags modularity + */ +import python + +from ModuleObject m, int n +where n = count(ModuleObject imp | imp = m.getAnImportedModule+() and imp != m) +select m.getModule(), n \ No newline at end of file diff --git a/python/ql/src/Resources/FileNotAlwaysClosed.py b/python/ql/src/Resources/FileNotAlwaysClosed.py new file mode 100644 index 000000000000..5f5f10345c79 --- /dev/null +++ b/python/ql/src/Resources/FileNotAlwaysClosed.py @@ -0,0 +1,15 @@ +f = open("filename") + ... # Actions to perform on file +f.close() +# File only closed if actions are completed successfully + +with open("filename") as f: + ...# Actions to perform on file +# File always closed + +f = open("filename") +try: + ... # Actions to perform on file +finally: + f.close() +# File always closed diff --git a/python/ql/src/Resources/FileNotAlwaysClosed.qhelp b/python/ql/src/Resources/FileNotAlwaysClosed.qhelp new file mode 100644 index 000000000000..71073caa47b5 --- /dev/null +++ b/python/ql/src/Resources/FileNotAlwaysClosed.qhelp @@ -0,0 +1,40 @@ + + + + +

    If a file is opened then it should always be closed again, even if an +exception is raised. +Failing to ensure that all files are closed may result in failure due to too +many open files.

    + +
    + + +

    Ensure that if you open a file it is always closed on exiting the method. +Wrap the code between the open() and close() +functions in a with statement or use a try...finally +statement. Using a with statement is preferred as it is shorter +and more readable.

    + +
    + +

    The following code shows examples of different ways of closing a file. In the first example, the +file is closed only if the method is exited successfully. In the other examples, the file is always +closed on exiting the method.

    + + + +
    + + + +
  • Python Language Reference: The with statement, + The try statement.
  • +
  • Python PEP 343: The "with" Statement.
  • + + + +
    +
    diff --git a/python/ql/src/Resources/FileNotAlwaysClosed.ql b/python/ql/src/Resources/FileNotAlwaysClosed.ql new file mode 100755 index 000000000000..870c041402eb --- /dev/null +++ b/python/ql/src/Resources/FileNotAlwaysClosed.ql @@ -0,0 +1,72 @@ +/** + * @name File is not always closed + * @description Opening a file without ensuring that it is always closed may cause resource leaks. + * @kind problem + * @tags efficiency + * correctness + * resources + * external/cwe/cwe-772 + * @problem.severity warning + * @sub-severity high + * @precision medium + * @id py/file-not-closed + */ + +import python +import FileOpen + +/** Whether resource is opened and closed in in a matched pair of methods, + * either __enter__ and __exit__ or __init__ and __del__ */ +predicate opened_in_enter_closed_in_exit(ControlFlowNode open) { + file_not_closed_at_scope_exit(open) and + exists(FunctionObject entry, FunctionObject exit | + open.getScope() = entry.getFunction() and + exists(ClassObject cls | + cls.declaredAttribute("__enter__") = entry and cls.declaredAttribute("__exit__") = exit + or + cls.declaredAttribute("__init__") = entry and cls.declaredAttribute("__del__") = exit + ) + and + exists(AttrNode attr_open, AttrNode attrclose | + attr_open.getScope() = entry.getFunction() and + attrclose.getScope() = exit.getFunction() and + expr_is_open(attr_open.(DefinitionNode).getValue(), open) and + attr_open.getName() = attrclose.getName() and + close_method_call(_, attrclose) + ) + ) +} + +predicate file_not_closed_at_scope_exit(ControlFlowNode open) { + exists(EssaVariable v | + BaseFlow::reaches_exit(v) and + var_is_open(v, open) and + not file_is_returned(v, open) + ) + or + call_to_open(open) and not exists(AssignmentDefinition def | def.getValue() = open) + and not exists(Return r | r.getValue() = open.getNode()) +} + +predicate file_not_closed_at_exception_exit(ControlFlowNode open, ControlFlowNode exit) { + exists(EssaVariable v | + exit.(RaisingNode).viableExceptionalExit(_, _) and + not closes_arg(exit, v.getSourceVariable()) and + not close_method_call(exit, v.getAUse().(NameNode)) and + var_is_open(v, open) and + v.getAUse() = exit.getAChild*() + ) +} + +/* Check to see if a file is opened but not closed or returned */ + +from ControlFlowNode defn, string message +where +not opened_in_enter_closed_in_exit(defn) and +( + file_not_closed_at_scope_exit(defn) and message = "File is opened but is not closed." + or + not file_not_closed_at_scope_exit(defn) and file_not_closed_at_exception_exit(defn, _) and message = "File may not be closed if an exception is raised." +) + +select defn.getNode(), message diff --git a/python/ql/src/Resources/FileOpen.qll b/python/ql/src/Resources/FileOpen.qll new file mode 100644 index 000000000000..ec07749587ff --- /dev/null +++ b/python/ql/src/Resources/FileOpen.qll @@ -0,0 +1,156 @@ +import python +import semmle.python.GuardedControlFlow +import semmle.python.dataflow.SsaDefinitions +import semmle.python.pointsto.Filters + +/** Holds if `open` is a call that returns a newly opened file */ +predicate call_to_open(ControlFlowNode open) { + exists(FunctionObject f | + function_opens_file(f) and + f.getACall() = open + ) and + /* If in `with` statement, then it will be automatically closed. So just treat as not opened */ + not exists(With w | w.getContextExpr() = open.getNode()) +} + +/** Holds if `n` refers to a file opened at `open` */ +predicate expr_is_open(ControlFlowNode n, ControlFlowNode open) { + call_to_open(open) and open = n + or + exists(EssaVariable v | + n instanceof NameNode and + var_is_open(v, open) | + n = v.getAUse() + or + wraps_file(n, v) + ) +} + +/** Holds if `call` wraps the object referred to by `v` and returns it */ +private predicate wraps_file(CallNode call, EssaVariable v) { + exists(ClassObject cls | + call = cls.getACall() and + call.getAnArg() = v.getAUse() + ) +} + +/** Holds if `var` refers to a file opened at `open` */ +predicate var_is_open(EssaVariable v, ControlFlowNode open) { + def_is_open(v.getDefinition(), open) and + /* If use in context expression in `with` statement, then it will be automatically closed. */ + not exists(With w | w.getContextExpr() = v.getAUse().getNode()) +} + +/** Holds if `test` will pass through an open file in variable `v` for the `sense` successor */ +predicate passes_open_files(Variable v, ControlFlowNode test, boolean sense) { + // `if fd.closed:` + exists(AttrNode closed | + closed = test and + closed.getObject("closed") = v.getAUse() + ) and sense = false + or + // `if fd ==/is ...:` most commonly `if fd is None:` + equality_test(test, v.getAUse(), sense.booleanNot(), _) + or + // `if fd:` + test = v.getAUse() and sense = true + or + exists(UnaryExprNode n | + n = test and + n.getNode().getOp() instanceof Not | + passes_open_files(v, n.getOperand(), sense.booleanNot()) + ) +} + +/* Helper for `def_is_open` to give better join order */ +private predicate passes_open_files(PyEdgeRefinement refinement) { + passes_open_files(refinement.getSourceVariable(), refinement.getPredecessor().getLastNode(), refinement.getSense()) +} + +/** Holds if `def` refers to a file opened at `open` */ +predicate def_is_open(EssaDefinition def, ControlFlowNode open) { + expr_is_open(def.(AssignmentDefinition).getValue(), open) + or + exists(PyEdgeRefinement refinement | + refinement = def | + var_is_open(refinement.getInput(), open) and + passes_open_files(refinement) + ) + or + exists(PyNodeRefinement refinement | + refinement = def | + not closes_file(def) and not wraps_file(refinement.getDefiningNode(), refinement.getInput()) and + var_is_open(refinement.getInput(), open) + ) + or + var_is_open(def.(PhiFunction).getAnInput(), open) +} + +/** Holds if `call` closes a file */ +predicate closes_file(EssaNodeRefinement call) { + closes_arg(call.(ArgumentRefinement).getDefiningNode(), call.getSourceVariable()) or + close_method_call(call.(MethodCallsiteRefinement).getCall(), call.getSourceVariable().(Variable).getAUse()) +} + +/** Holds if `call` closes its argument, which is an open file referred to by `v` */ +predicate closes_arg(CallNode call, Variable v) { + call.getAnArg() = v.getAUse() and + ( + exists(FunctionObject close | + call = close.getACall() and function_closes_file(close) + ) + or + call.getFunction().(NameNode).getId() = "close" + ) +} + +/** Holds if `call` closes its 'self' argument, which is an open file referred to by `v` */ +predicate close_method_call(CallNode call, ControlFlowNode self) { + call.getFunction().(AttrNode).getObject() = self and + exists(FunctionObject close | + call = close.getACall() and function_closes_file(close) + ) + or + call.getFunction().(AttrNode).getObject("close") = self +} + +predicate function_closes_file(FunctionObject close) { + close.hasLongName("os.close") + or + function_should_close_parameter(close.getFunction()) +} + +predicate function_should_close_parameter(Function func) { + exists(EssaDefinition def | + closes_file(def) and + def.getSourceVariable().(Variable).getScope() = func + ) +} + +predicate function_opens_file(FunctionObject f) { + f = theOpenFunction() + or + exists(EssaVariable v, Return ret | + ret.getScope() = f.getFunction() | + ret.getValue().getAFlowNode() = v.getAUse() and + var_is_open(v, _) + ) + or + exists(Return ret, FunctionObject callee | + ret.getScope() = f.getFunction() | + ret.getValue().getAFlowNode() = callee.getACall() and + function_opens_file(callee) + ) +} + +predicate file_is_returned(EssaVariable v, ControlFlowNode open) { + exists(NameNode n, Return ret | + var_is_open(v, open) and + v.getAUse() = n | + ret.getValue() = n.getNode() + or + ret.getValue().(Tuple).getAnElt() = n.getNode() + or + ret.getValue().(List).getAnElt() = n.getNode() + ) +} diff --git a/python/ql/src/Security/CWE-022/PathInjection.qhelp b/python/ql/src/Security/CWE-022/PathInjection.qhelp new file mode 100644 index 000000000000..4a4fb3f4bd78 --- /dev/null +++ b/python/ql/src/Security/CWE-022/PathInjection.qhelp @@ -0,0 +1,61 @@ + + + + +

    +Accessing files using paths constructed from user-controlled data can allow an attacker to access +unexpected resources. This can result in sensitive information being revealed or deleted, or an +attacker being able to influence behavior by modifying unexpected files. +

    +
    + + +

    +Validate user input before using it to construct a file path, either using an off-the-shelf library function +like werkzeug.utils.secure_filename, or by performing custom validation. +

    + +

    +Ideally, follow these rules: +

    + +
      +
    • Do not allow more than a single "." character.
    • +
    • Do not allow directory separators such as "/" or "\" (depending on the file system).
    • +
    • Do not rely on simply replacing problematic sequences such as "../". For example, after +applying this filter to ".../...//", the resulting string would still be "../".
    • +
    • Use a whitelist of known good patterns.
    • +
    +
    + + +

    +In the first example, a file name is read from an HTTP request and then used to access a file. +However, a malicious user could enter a file name that is an absolute path, such as +"/etc/passwd". +

    + +

    +In the second example, it appears that the user is restricted to opening a file within the +"user" home directory. However, a malicious user could enter a file name containing +special characters. For example, the string "../../../etc/passwd" will result in the code +reading the file located at "/server/static/images/../../../etc/passwd", which is the system's +password file. This file would then be sent back to the user, giving them access to all the +system's passwords. +

    + +

    +In the third example, the path used to access the file system is normalized before being checked against a +known prefix. This ensures that regardless of the user input, the resulting path is safe. +

    + + +
    + + +
  • OWASP: Path Traversal.
  • +
  • npm: werkzeug.utils.secure_filename.
  • +
    +
    diff --git a/python/ql/src/Security/CWE-022/PathInjection.ql b/python/ql/src/Security/CWE-022/PathInjection.ql new file mode 100644 index 000000000000..1b1eb33a5073 --- /dev/null +++ b/python/ql/src/Security/CWE-022/PathInjection.ql @@ -0,0 +1,31 @@ +/** + * @name Uncontrolled data used in path expression + * @description Accessing paths influenced by users can allow an attacker to access unexpected resources. + * @kind path-problem + * @problem.severity error + * @sub-severity high + * @precision high + * @id py/path-injection + * @tags correctness + * security + * external/owasp/owasp-a1 + * external/cwe/cwe-022 + * external/cwe/cwe-023 + * external/cwe/cwe-036 + * external/cwe/cwe-073 + * external/cwe/cwe-099 + */ + +import python +import semmle.python.security.Paths + +/* Sources */ +import semmle.python.web.HttpRequest + +/* Sinks */ +import semmle.python.security.injection.Path + + +from TaintedPathSource src, TaintedPathSink sink +where src.flowsTo(sink) +select sink.getSink(), src, sink, "This path depends on $@.", src.getSource(), "a user-provided value" \ No newline at end of file diff --git a/python/ql/src/Security/CWE-022/examples/tainted_path.py b/python/ql/src/Security/CWE-022/examples/tainted_path.py new file mode 100644 index 000000000000..b7366b9b6cfd --- /dev/null +++ b/python/ql/src/Security/CWE-022/examples/tainted_path.py @@ -0,0 +1,37 @@ +import os.path + + +urlpatterns = [ + # Route to user_picture + url(r'^user-pic1$', user_picture1, name='user-picture1'), + url(r'^user-pic2$', user_picture2, name='user-picture2'), + url(r'^user-pic3$', user_picture3, name='user-picture3') +] + + +def user_picture1(request): + """A view that is vulnerable to malicious file access.""" + base_path = '/server/static/images' + filename = request.GET.get('p') + # BAD: This could read any file on the file system + data = open(filename, 'rb').read() + return HttpResponse(data) + +def user_picture2(request): + """A view that is vulnerable to malicious file access.""" + base_path = '/server/static/images' + filename = request.GET.get('p') + # BAD: This could still read any file on the file system + data = open(os.path.join(base_path, filename), 'rb').read() + return HttpResponse(data) + +def user_picture3(request): + """A view that is not vulnerable to malicious file access.""" + base_path = '/server/static/images' + filename = request.GET.get('p') + #GOOD -- Verify with normalised version of path + fullpath = os.path.normpath(os.path.join(base_path, filename)) + if not fullpath.startswith(base_path): + raise SecurityException() + data = open(fullpath, 'rb').read() + return HttpResponse(data) diff --git a/python/ql/src/Security/CWE-078/CommandInjection.qhelp b/python/ql/src/Security/CWE-078/CommandInjection.qhelp new file mode 100644 index 000000000000..0423269c919a --- /dev/null +++ b/python/ql/src/Security/CWE-078/CommandInjection.qhelp @@ -0,0 +1,41 @@ + + + +

    Code that passes user input directly to +exec, eval, or some other library +routine that executes a command, allows the user to execute malicious +code.

    + +
    + + +

    If possible, use hard-coded string literals to specify the command to run +or the library to load. Instead of passing the user input directly to the +process or library function, examine the user input and then choose +among hard-coded string literals.

    + +

    If the applicable libraries or commands cannot be determined at +compile time, then add code to verify that the user input string is +safe before using it.

    + +
    + + +

    The following example shows two functions. The first is unsafe as it takes a shell script that can be changed +by a user, and passes it straight to subprocess.call() without examining it first. +The second is safe as it selects the command from a predefined white-list.

    + + + +
    + + +
  • +OWASP: +Command Injection. +
  • + +
    +
    diff --git a/python/ql/src/Security/CWE-078/CommandInjection.ql b/python/ql/src/Security/CWE-078/CommandInjection.ql new file mode 100755 index 000000000000..639aab3725fe --- /dev/null +++ b/python/ql/src/Security/CWE-078/CommandInjection.ql @@ -0,0 +1,28 @@ +/** + * @name Uncontrolled command line + * @description Using externally controlled strings in a command line may allow a malicious + * user to change the meaning of the command. + * @kind path-problem + * @problem.severity error + * @sub-severity high + * @precision high + * @id py/command-line-injection + * @tags correctness + * security + * external/owasp/owasp-a1 + * external/cwe/cwe-078 + * external/cwe/cwe-088 + */ + +import python +import semmle.python.security.Paths + +/* Sources */ +import semmle.python.web.HttpRequest + +/* Sinks */ +import semmle.python.security.injection.Command + +from TaintedPathSource src, TaintedPathSink sink +where src.flowsTo(sink) +select sink.getSink(), src, sink, "This command depends on $@.", src.getSource(), "a user-provided value" diff --git a/python/ql/src/Security/CWE-078/examples/command_injection.py b/python/ql/src/Security/CWE-078/examples/command_injection.py new file mode 100644 index 000000000000..54cfb275165c --- /dev/null +++ b/python/ql/src/Security/CWE-078/examples/command_injection.py @@ -0,0 +1,24 @@ + +urlpatterns = [ + # Route to command_execution + url(r'^command-ex1$', command_execution_unsafe, name='command-execution-unsafe'), + url(r'^command-ex2$', command_execution_safe, name='command-execution-safe') +] + +COMMANDS = { + "list" :"ls", + "stat" : "stat" +} + +def command_execution_unsafe(request): + if request.method == 'POST': + action = request.POST.get('action', '') + #BAD -- No sanitizing of input + subprocess.call(["application", action]) + +def command_execution_safe(request): + if request.method == 'POST': + action = request.POST.get('action', '') + #GOOD -- Use a whitelist + subprocess.call(["application", COMMAND[action]]) + diff --git a/python/ql/src/Security/CWE-079/ReflectedXss.qhelp b/python/ql/src/Security/CWE-079/ReflectedXss.qhelp new file mode 100644 index 000000000000..8cdeb4d3e793 --- /dev/null +++ b/python/ql/src/Security/CWE-079/ReflectedXss.qhelp @@ -0,0 +1,45 @@ + + + + +

    +Directly writing user input (for example, an HTTP request parameter) to a webpage +without properly sanitizing the input first, allows for a cross-site scripting vulnerability. +

    +
    + + +

    +To guard against cross-site scripting, consider escaping the input before writing user input to the page. +The standard library provides escaping functions: html.escape() for Python 3.2 upwards +or cgi.escape() older versions of Python. +Most frameworks also provide their own escaping functions, for example flask.escape(). +

    +
    + + +

    +The following example is a minimal flask app which shows a safe and unsafe way to render the given name back to the page. +The first view is unsafe as first_name is not escaped, leaving the page vulnerable to cross-site scripting attacks. +The second view is safe as first_name is escaped, so it is not vulnerable to cross-site scripting attacks. +

    + +
    + + +
  • +OWASP: +XSS +(Cross Site Scripting) Prevention Cheat Sheet. +
  • +
  • +Wikipedia: Cross-site scripting. +
  • +
  • +Python Library Reference: +html.escape(). +
  • +
    +
    diff --git a/python/ql/src/Security/CWE-079/ReflectedXss.ql b/python/ql/src/Security/CWE-079/ReflectedXss.ql new file mode 100644 index 000000000000..3a77e0559f63 --- /dev/null +++ b/python/ql/src/Security/CWE-079/ReflectedXss.ql @@ -0,0 +1,30 @@ +/** + * @name Reflected server-side cross-site scripting + * @description Writing user input directly to a web page + * allows for a cross-site scripting vulnerability. + * @kind path-problem + * @problem.severity error + * @sub-severity high + * @precision high + * @id py/reflective-xss + * @tags security + * external/cwe/cwe-079 + * external/cwe/cwe-116 + */ + +import python +import semmle.python.security.Paths + +/* Sources */ +import semmle.python.web.HttpRequest + +/* Sinks */ + +import semmle.python.web.HttpResponse + +/* Flow */ +import semmle.python.security.strings.Untrusted + +from TaintedPathSource src, TaintedPathSink sink +where src.flowsTo(sink) +select sink.getSink(), src, sink, "Cross-site scripting vulnerability due to $@.", src.getSource(), "user-provided value" diff --git a/python/ql/src/Security/CWE-079/examples/xss.py b/python/ql/src/Security/CWE-079/examples/xss.py new file mode 100644 index 000000000000..fbe5795047ba --- /dev/null +++ b/python/ql/src/Security/CWE-079/examples/xss.py @@ -0,0 +1,13 @@ +from flask import Flask, request, make_response, escape + +app = Flask(__name__) + +@app.route('/unsafe') +def unsafe(): + first_name = request.args.get('name', '') + return make_response("Your name is " + first_name) + +@app.route('/safe') +def safe(): + first_name = request.args.get('name', '') + return make_response("Your name is " + escape(first_name)) diff --git a/python/ql/src/Security/CWE-089/SqlInjection.qhelp b/python/ql/src/Security/CWE-089/SqlInjection.qhelp new file mode 100644 index 000000000000..e976401a6b5f --- /dev/null +++ b/python/ql/src/Security/CWE-089/SqlInjection.qhelp @@ -0,0 +1,47 @@ + + + + +

    +If a database query (such as a SQL or NoSQL query) is built from +user-provided data without sufficient sanitization, a user +may be able to run malicious database queries. +

    +
    + + +

    +Most database connector libraries offer a way of safely +embedding untrusted data into a query by means of query parameters +or prepared statements. +

    +
    + + +

    +In the following snippet, from an example django app, +a name is stored in the database using two different queries. +

    + +

    +In the first case, the query string is built by +directly using string formatting from a user-supplied request attribute. +The parameter may include quote characters, so this +code is vulnerable to a SQL injection attack. +

    + +

    +In the second case, the user-supplied request attribute is passed +to the database using query parameters. +

    + + +
    + + +
  • Wikipedia: SQL injection.
  • +
  • OWASP: SQL Injection Prevention Cheat Sheet.
  • +
    +
    diff --git a/python/ql/src/Security/CWE-089/SqlInjection.ql b/python/ql/src/Security/CWE-089/SqlInjection.ql new file mode 100755 index 000000000000..35274729418d --- /dev/null +++ b/python/ql/src/Security/CWE-089/SqlInjection.ql @@ -0,0 +1,28 @@ +/** + * @name SQL query built from user-controlled sources + * @description Building a SQL query from user-controlled sources is vulnerable to insertion of + * malicious SQL code by the user. + * @kind path-problem + * @problem.severity error + * @precision high + * @id py/sql-injection + * @tags security + * external/cwe/cwe-089 + * external/owasp/owasp-a1 + */ + +import python +import semmle.python.security.Paths + +/* Sources */ +import semmle.python.web.HttpRequest + +/* Sinks */ +import semmle.python.security.injection.Sql +import semmle.python.web.django.Db +import semmle.python.web.django.Model + + +from TaintedPathSource src, TaintedPathSink sink +where src.flowsTo(sink) +select sink.getSink(), src, sink, "This SQL query depends on $@.", src.getSource(), "a user-provided value" diff --git a/python/ql/src/Security/CWE-089/examples/sql_injection.py b/python/ql/src/Security/CWE-089/examples/sql_injection.py new file mode 100644 index 000000000000..541c580f7127 --- /dev/null +++ b/python/ql/src/Security/CWE-089/examples/sql_injection.py @@ -0,0 +1,21 @@ + +from django.conf.urls import patterns, url +from django.db import connection + + +def save_name(request): + + if request.method == 'POST': + name = request.POST.get('name') + curs = connection.cursor() + #BAD -- Using string formatting + curs.execute( + "insert into names_file ('name') values ('%s')" % name) + #GOOD -- Using parameters + curs.execute( + "insert into names_file ('name') values ('%s')", name) + + +urlpatterns = patterns(url(r'^save_name/$', + upload, name='save_name')) + diff --git a/python/ql/src/Security/CWE-094/CodeInjection.qhelp b/python/ql/src/Security/CWE-094/CodeInjection.qhelp new file mode 100644 index 000000000000..8d7aab476a64 --- /dev/null +++ b/python/ql/src/Security/CWE-094/CodeInjection.qhelp @@ -0,0 +1,46 @@ + + + + +

    +Directly evaluating user input (for example, an HTTP request parameter) as code without properly +sanitizing the input first allows an attacker arbitrary code execution. This can occur when user +input is passed to code that interprets it as an expression to be +evaluated, such as eval or exec. +

    +
    + + +

    +Avoid including user input in any expression that may be dynamically evaluated. If user input must +be included, use context-specific escaping before including it. +It is important that the correct escaping is used for the type of evaluation that will occur. +

    +
    + + +

    +The following example shows two functions setting a name from a request. +The first function uses exec to execute the setname function. +This is dangerous as it can allow a malicious user to execute arbitrary code on the server. +For example, the user could supply the value "' + subprocess.call('rm -rf') + '" +to destroy the server's file system. +The second function calls the setname function directly and is thus safe. + +

    + + +
    + + +
  • +OWASP: +Code Injection. +
  • +
  • +Wikipedia: Code Injection. +
  • +
    +
    diff --git a/python/ql/src/Security/CWE-094/CodeInjection.ql b/python/ql/src/Security/CWE-094/CodeInjection.ql new file mode 100644 index 000000000000..2d511a5dae41 --- /dev/null +++ b/python/ql/src/Security/CWE-094/CodeInjection.ql @@ -0,0 +1,29 @@ +/** + * @name Code injection + * @description Interpreting unsanitized user input as code allows a malicious user arbitrary + * code execution. + * @kind path-problem + * @problem.severity error + * @sub-severity high + * @precision high + * @id py/code-injection + * @tags security + * external/owasp/owasp-a1 + * external/cwe/cwe-094 + * external/cwe/cwe-079 + * external/cwe/cwe-116 + */ + +import python +import semmle.python.security.Paths + +/* Sources */ +import semmle.python.web.HttpRequest + +/* Sinks */ +import semmle.python.security.injection.Exec + + +from TaintedPathSource src, TaintedPathSink sink +where src.flowsTo(sink) +select sink.getSink(), src, sink, "$@ flows to here and is interpreted as code.", src.getSource(), "User-provided value" diff --git a/python/ql/src/Security/CWE-094/examples/code_injection.py b/python/ql/src/Security/CWE-094/examples/code_injection.py new file mode 100644 index 000000000000..d70f9118ad52 --- /dev/null +++ b/python/ql/src/Security/CWE-094/examples/code_injection.py @@ -0,0 +1,18 @@ + +urlpatterns = [ + # Route to code_execution + url(r'^code-ex1$', code_execution_bad, name='code-execution-bad'), + url(r'^code-ex2$', code_execution_good, name='code-execution-good') +] + +def code_execution(request): + if request.method == 'POST': + first_name = base64.decodestring(request.POST.get('first_name', '')) + #BAD -- Allow user to define code to be run. + exec("setname('%s')" % first_name) + +def code_execution(request): + if request.method == 'POST': + first_name = base64.decodestring(request.POST.get('first_name', '')) + #GOOD --Call code directly + setname(first_name) diff --git a/python/ql/src/Security/CWE-209/StackTraceExposure.py b/python/ql/src/Security/CWE-209/StackTraceExposure.py new file mode 100644 index 000000000000..169dc091dddd --- /dev/null +++ b/python/ql/src/Security/CWE-209/StackTraceExposure.py @@ -0,0 +1,25 @@ +from flask import Flask +app = Flask(__name__) + + +import traceback + +def do_computation(): + raise Exception("Secret info") + +# BAD +@app.route('/bad') +def server_bad(): + try: + do_computation() + except Exception as e: + return traceback.format_exc() + +# GOOD +@app.route('/good') +def server_good(): + try: + do_computation() + except Exception as e: + log(traceback.format_exc()) + return "An internal error has occurred!" diff --git a/python/ql/src/Security/CWE-209/StackTraceExposure.qhelp b/python/ql/src/Security/CWE-209/StackTraceExposure.qhelp new file mode 100644 index 000000000000..86ecdbdc0d81 --- /dev/null +++ b/python/ql/src/Security/CWE-209/StackTraceExposure.qhelp @@ -0,0 +1,52 @@ + + + + +

    +Software developers often add stack traces to error messages, as a +debugging aid. Whenever that error message occurs for an end user, the +developer can use the stack trace to help identify how to fix the +problem. In particular, stack traces can tell the developer more about +the sequence of events that led to a failure, as opposed to merely the +final state of the software when the error occurred. +

    + +

    +Unfortunately, the same information can be useful to an attacker. +The sequence of class names in a stack trace can reveal the structure +of the application as well as any internal components it relies on. +Furthermore, the error message at the top of a stack trace can include +information such as server-side file names and SQL code that the +application relies on, allowing an attacker to fine-tune a subsequent +injection attack. +

    +
    + + +

    +Send the user a more generic error message that reveals less information. +Either suppress the stack trace entirely, or log it only on the server. +

    +
    + + +

    +In the following example, an exception is handled in two different +ways. In the first version, labeled BAD, the exception is sent back to +the remote user by returning it from the function. As such, +the user is able to see a detailed stack trace, which may contain +sensitive information. In the second version, the error message is +logged only on the server, and a generic error message is displayed to +the user. That way, the developers can still access and use the error +log, but remote users will not see the information. +

    + + +
    + + +
  • OWASP: Information Leak.
  • +
    +
    diff --git a/python/ql/src/Security/CWE-209/StackTraceExposure.ql b/python/ql/src/Security/CWE-209/StackTraceExposure.ql new file mode 100644 index 000000000000..4a1452655ed9 --- /dev/null +++ b/python/ql/src/Security/CWE-209/StackTraceExposure.ql @@ -0,0 +1,23 @@ +/** + * @name Information exposure through an exception + * @description Leaking information about an exception, such as messages and stack traces, to an + * external user can expose implementation details that are useful to an attacker for + * developing a subsequent exploit. + * @kind path-problem + * @problem.severity error + * @precision high + * @id py/stack-trace-exposure + * @tags security + * external/cwe/cwe-209 + * external/cwe/cwe-497 + */ + +import python +import semmle.python.security.Paths + +import semmle.python.security.Exceptions +import semmle.python.web.HttpResponse + +from TaintedPathSource src, TaintedPathSink sink +where src.flowsTo(sink) +select sink.getSink(), src, sink, "$@ may be exposed to an external user", src.getSource(), "Error information" diff --git a/python/ql/src/Security/CWE-215/FlaskDebug.py b/python/ql/src/Security/CWE-215/FlaskDebug.py new file mode 100644 index 000000000000..683f99dcb550 --- /dev/null +++ b/python/ql/src/Security/CWE-215/FlaskDebug.py @@ -0,0 +1,9 @@ +from flask import Flask + +app = Flask(__name__) + +@app.route('/crash') +def main(): + raise Exception() + +app.run(debug=True) diff --git a/python/ql/src/Security/CWE-215/FlaskDebug.qhelp b/python/ql/src/Security/CWE-215/FlaskDebug.qhelp new file mode 100644 index 000000000000..b70690171386 --- /dev/null +++ b/python/ql/src/Security/CWE-215/FlaskDebug.qhelp @@ -0,0 +1,39 @@ + + + +

    + Running a Flask application with debug mode enabled may allow an + attacker to gain access through the Werkzeug debugger. +

    + +
    + + +

    + Ensure that Flask applications that are run in a production + environment have debugging disabled. +

    + +
    + + +

    + Running the following code starts a Flask webserver that has + debugging enabled. By visiting /crash, it is possible + to gain access to the debugger, and run arbitrary code through the + interactive debugger. +

    + + + +
    + + +
  • Flask Quickstart Documentation: Debug Mode.
  • +
  • Werkzeug Documentation: Debugging Applications.
  • +
    + +
    + diff --git a/python/ql/src/Security/CWE-215/FlaskDebug.ql b/python/ql/src/Security/CWE-215/FlaskDebug.ql new file mode 100644 index 000000000000..6886e4192138 --- /dev/null +++ b/python/ql/src/Security/CWE-215/FlaskDebug.ql @@ -0,0 +1,23 @@ +/** + * @name Flask app is run in debug mode + * @description Running a Flask app in debug mode may allow an attacker to run arbitrary code through the Werkzeug debugger. + * @kind problem + * @problem.severity error + * @precision high + * @id py/flask-debug + * @tags security + * external/cwe/cwe-215 + * external/cwe/cwe-489 + */ + +import python + +import semmle.python.web.flask.General + + +from CallNode call, Object isTrue +where + call = theFlaskClass().declaredAttribute("run").(FunctionObject).getACall() and + call.getArgByName("debug").refersTo(isTrue) and + isTrue.booleanValue() = true +select call, "A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger." diff --git a/python/ql/src/Security/CWE-295/RequestWithoutValidation.qhelp b/python/ql/src/Security/CWE-295/RequestWithoutValidation.qhelp new file mode 100644 index 000000000000..b581c4f4a010 --- /dev/null +++ b/python/ql/src/Security/CWE-295/RequestWithoutValidation.qhelp @@ -0,0 +1,36 @@ + + + + +

    +Encryption is key to the security of most, if not all, online communication. +Using Transport Layer Security (TLS) can ensure that communication cannot be interrupted by an interloper. +For this reason, is is unwise to disable the verification that TLS provides. +Functions in the requests module provide verification by default, and it is only when +explicitly turned off using verify=False that no verification occurs. +

    +
    + + +

    +Never use verify=False when making a request. +

    +
    + + +

    +The example shows two unsafe calls to semmle.com, followed by various safe alternatives. +

    + + +
    + + +
  • +Python requests documentation: SSL Cert Verification. +
  • +
    +
    + diff --git a/python/ql/src/Security/CWE-295/RequestWithoutValidation.ql b/python/ql/src/Security/CWE-295/RequestWithoutValidation.ql new file mode 100644 index 000000000000..4413a986704c --- /dev/null +++ b/python/ql/src/Security/CWE-295/RequestWithoutValidation.ql @@ -0,0 +1,36 @@ +/** + * @name Request without certificate validation + * @description Making a request without certificate validation can allow man-in-the-middle attacks. + * @kind problem + * @problem.severity error + * @precision medium + * @id py/request-without-cert-validation + * @tags security + * external/cwe/cwe-295 + */ + +import python + +import semmle.python.web.Http + + +FunctionObject requestFunction() { + exists(ModuleObject req | + req.getName() = "requests" and + result = req.getAttribute(httpVerbLower()) + ) +} + +/** requests treats None as the default and all other "falsey" values as False */ +predicate falseNotNone(Object o) { + o.booleanValue() = false and not o = theNoneObject() +} + +from CallNode call, FunctionObject func, Object falsey, ControlFlowNode origin +where +func = requestFunction() and +func.getACall() = call and +falseNotNone(falsey) and +call.getArgByName("verify").refersTo(falsey, origin) + +select call, "Call to $@ with verify=$@", func, "requests." + func.getName(), origin, "False" diff --git a/python/ql/src/Security/CWE-295/examples/make_request.py b/python/ql/src/Security/CWE-295/examples/make_request.py new file mode 100644 index 000000000000..ce2f11dcb33c --- /dev/null +++ b/python/ql/src/Security/CWE-295/examples/make_request.py @@ -0,0 +1,19 @@ +import requests + +#Unsafe requests + +requests.get('https://semmle.com', verify=False) # UNSAFE +requests.get('https://semmle.com', verify=0) # UNSAFE + +#Various safe options + +requests.get('https://semmle.com', verify=True) # Explicitly safe +requests.get('https://semmle.com', verify="/path/to/cert/") +requests.get('https://semmle.com') # The default is to verify. + +#Wrapper to ensure safety + +def make_safe_request(url, verify_cert): + if not verify_cert: + raise Exception("Trying to make unsafe request") + return requests.get(url, verify_cert) diff --git a/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.qhelp b/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.qhelp new file mode 100644 index 000000000000..6cc787e52e48 --- /dev/null +++ b/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.qhelp @@ -0,0 +1,57 @@ + + + +

    + Using broken or weak cryptographic algorithms can leave data + vulnerable to being decrypted or forged by an attacker. +

    + +

    + Many cryptographic algorithms provided by cryptography + libraries are known to be weak, or flawed. Using such an + algorithm means that encrypted or hashed data is less + secure than it appears to be. +

    + +
    + + +

    + Ensure that you use a strong, modern cryptographic + algorithm. Use at least AES-128 or RSA-2048 for + encryption, and SHA-2 or SHA-3 for secure hashing. +

    + +
    + + +

    + The following code uses the pycrypto + library to encrypt some secret data. When you create a cipher using + pycrypto you must specify the encryption + algorithm to use. The first example uses DES, which is an + older algorithm that is now considered weak. The second + example uses Blowfish, which is a stronger more modern algorithm. +

    + + + +

    + WARNING: Although the second example above is more robust, + pycrypto is no longer actively maintained so we recommend using cryptography instead. +

    + +
    + + +
  • NIST, FIPS 140 Annex a: Approved Security Functions.
  • +
  • NIST, SP 800-131A: Transitions: Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths.
  • +
  • OWASP: Rule + - Use strong approved cryptographic algorithms. +
  • +
    + +
    diff --git a/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql b/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql new file mode 100644 index 000000000000..e4dc7855f26f --- /dev/null +++ b/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql @@ -0,0 +1,18 @@ +/** + * @name Use of a broken or weak cryptographic algorithm + * @description Using broken or weak cryptographic algorithms can compromise security. + * @kind problem + * @problem.severity warning + * @precision high + * @id py/weak-cryptographic-algorithm + * @tags security + * external/cwe/cwe-327 + */ +import python +import semmle.python.security.SensitiveData +import semmle.python.security.Crypto + +from SensitiveDataSource src, WeakCryptoSink sink +where src.flowsToSink(sink) + +select sink, "Sensitive data from $@ is used in a broken or weak cryptographic algorithm.", src , src.toString() diff --git a/python/ql/src/Security/CWE-327/examples/broken_crypto.py b/python/ql/src/Security/CWE-327/examples/broken_crypto.py new file mode 100644 index 000000000000..ef9fc75e889b --- /dev/null +++ b/python/ql/src/Security/CWE-327/examples/broken_crypto.py @@ -0,0 +1,13 @@ +from Crypto.Cipher import DES, Blowfish + +cipher = DES.new(SECRET_KEY) + +def send_encrypted(channel, message): + channel.send(cipher.encrypt(message)) # BAD: weak encryption + + +cipher = Blowfish.new(SECRET_KEY) + +def send_encrypted(channel, message): + channel.send(cipher.encrypt(message)) # GOOD: strong encryption + diff --git a/python/ql/src/Security/CWE-502/JsonGood.py b/python/ql/src/Security/CWE-502/JsonGood.py new file mode 100644 index 000000000000..89947cb0e5c8 --- /dev/null +++ b/python/ql/src/Security/CWE-502/JsonGood.py @@ -0,0 +1,10 @@ + +from django.conf.urls import url +import json + +def safe(pickled): + return json.loads(pickled) + +urlpatterns = [ + url(r'^(?P.*)$', safe) +] diff --git a/python/ql/src/Security/CWE-502/UnpicklingBad.py b/python/ql/src/Security/CWE-502/UnpicklingBad.py new file mode 100644 index 000000000000..0f8112a28ae3 --- /dev/null +++ b/python/ql/src/Security/CWE-502/UnpicklingBad.py @@ -0,0 +1,10 @@ + +from django.conf.urls import url +import pickle + +def unsafe(pickled): + return pickle.loads(pickled) + +urlpatterns = [ + url(r'^(?P.*)$', unsafe) +] \ No newline at end of file diff --git a/python/ql/src/Security/CWE-502/UnsafeDeserialization.qhelp b/python/ql/src/Security/CWE-502/UnsafeDeserialization.qhelp new file mode 100644 index 000000000000..f298e62695f5 --- /dev/null +++ b/python/ql/src/Security/CWE-502/UnsafeDeserialization.qhelp @@ -0,0 +1,61 @@ + + + + +

    +Deserializing untrusted data using any deserialization framework that +allows the construction of arbitrary serializable objects is easily exploitable +and in many cases allows an attacker to execute arbitrary code. Even before a +deserialized object is returned to the caller of a deserialization method a lot +of code may have been executed, including static initializers, constructors, +and finalizers. Automatic deserialization of fields means that an attacker may +craft a nested combination of objects on which the executed initialization code +may have unforeseen effects, such as the execution of arbitrary code. +

    +

    +There are many different serialization frameworks. This query currently +supports Pickle, Marshal and Yaml. +

    +
    + + +

    +Avoid deserialization of untrusted data if at all possible. If the +architecture permits it then use other formats instead of serialized objects, +for example JSON. +

    +
    + + +

    +The following example calls pickle.loads directly on a +value provided by an incoming HTTP request. Pickle then creates a new value from untrusted data, and is +therefore inherently unsafe. +

    + + +

    +Changing the code to use json.loads instead of pickle.loads removes the vulnerability. +

    + + +
    + + + +
  • +OWASP vulnerability description: +Deserialization of untrusted data. +
  • +
  • +OWASP guidance on deserializing objects: +Deserialization Cheat Sheet. +
  • +
  • +Talks by Chris Frohoff & Gabriel Lawrence: + +AppSecCali 2015: Marshalling Pickles - how deserializing objects will ruin your day +
  • +
    + +
    diff --git a/python/ql/src/Security/CWE-502/UnsafeDeserialization.ql b/python/ql/src/Security/CWE-502/UnsafeDeserialization.ql new file mode 100644 index 000000000000..fa48c63db9d8 --- /dev/null +++ b/python/ql/src/Security/CWE-502/UnsafeDeserialization.ql @@ -0,0 +1,30 @@ +/** + * @name Deserializing untrusted input + * @description Deserializing user-controlled data may allow attackers to execute arbitrary code. + * @kind path-problem + * @id py/unsafe-deserialization + * @problem.severity error + * @sub-severity high + * @precision high + * @tags external/cwe/cwe-502 + * security + * serialization + */ +import python + +// Sources -- Any untrusted input +import semmle.python.web.HttpRequest +import semmle.python.security.Paths + +// Flow -- untrusted string +import semmle.python.security.strings.Untrusted + +// Sink -- Unpickling and other deserialization formats. +import semmle.python.security.injection.Pickle +import semmle.python.security.injection.Marshal +import semmle.python.security.injection.Yaml + + +from TaintedPathSource src, TaintedPathSink sink +where src.flowsTo(sink) +select sink.getSink(), src, sink, "Deserializing of $@.", src.getSource(), "untrusted input" diff --git a/python/ql/src/Security/CWE-601/UrlRedirect.qhelp b/python/ql/src/Security/CWE-601/UrlRedirect.qhelp new file mode 100644 index 000000000000..c2e053f030ba --- /dev/null +++ b/python/ql/src/Security/CWE-601/UrlRedirect.qhelp @@ -0,0 +1,42 @@ + + + + +

    +Directly incorporating user input into a URL redirect request without validating the input +can facilitate phishing attacks. In these attacks, unsuspecting users can be redirected to a +malicious site that looks very similar to the real site they intend to visit, but which is +controlled by the attacker. +

    +
    + + +

    +To guard against untrusted URL redirection, it is advisable to avoid putting user input +directly into a redirect URL. Instead, maintain a list of authorized +redirects on the server; then choose from that list based on the user input provided. +

    +
    + + +

    +The following example shows an HTTP request parameter being used directly in a URL redirect +without validating the input, which facilitates phishing attacks: +

    + + + +

    +One way to remedy the problem is to validate the user input against a known fixed string +before doing the redirection: +

    + + +
    + + +
  • OWASP: + XSS Unvalidated Redirects and Forwards Cheat Sheet.
  • +
    + +
    diff --git a/python/ql/src/Security/CWE-601/UrlRedirect.ql b/python/ql/src/Security/CWE-601/UrlRedirect.ql new file mode 100644 index 000000000000..4734c540bc38 --- /dev/null +++ b/python/ql/src/Security/CWE-601/UrlRedirect.ql @@ -0,0 +1,34 @@ +/** + * @name URL redirection from remote source + * @description URL redirection based on unvalidated user input + * may cause redirection to malicious web sites. + * @kind path-problem + * @problem.severity error + * @sub-severity low + * @id py/url-redirection + * @tags security + * external/cwe/cwe-601 + * @precision high + */ + +import python +import semmle.python.security.Paths + +import semmle.python.web.HttpRedirect +import semmle.python.web.HttpRequest +import semmle.python.security.strings.Untrusted + +/** Url redirection is a problem only if the user controls the prefix of the URL */ +class UntrustedPrefixStringKind extends UntrustedStringKind { + + override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { + result = UntrustedStringKind.super.getTaintForFlowStep(fromnode, tonode) and + not tonode.(BinaryExprNode).getRight() = fromnode + } + +} + +from TaintedPathSource src, TaintedPathSink sink +where src.flowsTo(sink) +select sink.getSink(), src, sink, "Untrusted URL redirection due to $@.", src.getSource(), "a user-provided value" + diff --git a/python/ql/src/Security/CWE-601/examples/redirect_bad.py b/python/ql/src/Security/CWE-601/examples/redirect_bad.py new file mode 100644 index 000000000000..161edd70ec3f --- /dev/null +++ b/python/ql/src/Security/CWE-601/examples/redirect_bad.py @@ -0,0 +1,8 @@ +from flask import Flask, request, redirect + +app = Flask(__name__) + +@app.route('/') +def hello(): + target = files = request.args.get('target', '') + return redirect(target, code=302) diff --git a/python/ql/src/Security/CWE-601/examples/redirect_good.py b/python/ql/src/Security/CWE-601/examples/redirect_good.py new file mode 100644 index 000000000000..c93b0f98a00d --- /dev/null +++ b/python/ql/src/Security/CWE-601/examples/redirect_good.py @@ -0,0 +1,13 @@ +from flask import Flask, request, redirect + +VALID_REDIRECT = "http://cwe.mitre.org/data/definitions/601.html" + +app = Flask(__name__) + +@app.route('/') +def hello(): + target = files = request.args.get('target', '') + if target == VALID_REDIRECT: + return redirect(target, code=302) + else: + ... # Error diff --git a/python/ql/src/Security/CWE-798/HardcodedCredentials.py b/python/ql/src/Security/CWE-798/HardcodedCredentials.py new file mode 100644 index 000000000000..6eb54c567f8e --- /dev/null +++ b/python/ql/src/Security/CWE-798/HardcodedCredentials.py @@ -0,0 +1,19 @@ +import hashlib +import binascii + +def process_request(request): + password = request.GET["password"] + + # BAD: Inbound authentication made by comparison to string literal + if password == "myPa55word": + redirect("login") + + hashed_password = load_from_config('hashed_password', CONFIG_FILE) + salt = load_from_config('salt', CONFIG_FILE) + + #GOOD: Inbound authentication made by comparing to a hash password from a config file. + dk = hashlib.pbkdf2_hmac('sha256', password, salt, 100000) + hashed_input = binascii.hexlify(dk) + if hashed_input == hashed_password: + redirect("login") + diff --git a/python/ql/src/Security/CWE-798/HardcodedCredentials.qhelp b/python/ql/src/Security/CWE-798/HardcodedCredentials.qhelp new file mode 100644 index 000000000000..df7d81792b6d --- /dev/null +++ b/python/ql/src/Security/CWE-798/HardcodedCredentials.qhelp @@ -0,0 +1,82 @@ + + + + +

    +Including unencrypted hard-coded inbound or outbound authentication credentials within source code +or configuration files is dangerous because the credentials may be easily discovered. +

    +

    +Source or configuration files containing hard-coded credentials may be visible to an attacker. For +example, the source code may be open source, or it may be leaked or accidentally revealed. +

    +

    +For inbound authentication, hard-coded credentials may allow unauthorized access to the system. This +is particularly problematic if the credential is hard-coded in the source code, because it cannot be +disabled easily. For outbound authentication, the hard-coded credentials may provide an attacker with +privileged information or unauthorized access to some other system. +

    + +
    + + +

    +Remove hard-coded credentials, such as user names, passwords and certificates, from source code, +placing them in configuration files or other data stores if necessary. If possible, store +configuration files including credential data separately from the source code, in a secure location +with restricted access. +

    + +

    +For outbound authentication details, consider encrypting the credentials or the enclosing data +stores or configuration files, and using permissions to restrict access. +

    + +

    +For inbound authentication details, consider hashing passwords using standard library functions +where possible. For example, hashlib.pbkdf2_hmac. +

    + +
    + + +

    +The following examples shows different types of inbound and outbound authentication. +

    + +

    +In the first case, we accept a password from a remote user, and compare it against a plaintext +string literal. If an attacker acquires the source code they can observe +the password, and can log in to the system. Furthermore, if such an intrusion was discovered, the +application would need to be rewritten and redeployed in order to change the password. +

    + +

    +In the second case, the password is compared to a hashed and salted password stored in a +configuration file, using hashlib.pbkdf2_hmac. +In this case, access to the source code or the assembly would not reveal the password to an +attacker. Even access to the configuration file containing the password hash and salt would be of +little value to an attacker, as it is usually extremely difficult to reverse engineer the password +from the hash and salt. +

    + +

    +In the final case, a password is changed to a new, hard-coded value. If an attacker has access to +the source code, they will be able to observe the new password. +

    + + + +
    + + +
  • +OWASP: +XSS +Use of hard-coded password. +
  • + +
    +
    diff --git a/python/ql/src/Security/CWE-798/HardcodedCredentials.ql b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql new file mode 100644 index 000000000000..72f45f204efa --- /dev/null +++ b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql @@ -0,0 +1,143 @@ +/** + * @name Hard-coded credentials + * @description Credentials are hard coded in the source code of the application. + * @kind problem + * @problem.severity error + * @precision medium + * @id py/hardcoded-credentials + * @tags security + * external/cwe/cwe-259 + * external/cwe/cwe-321 + * external/cwe/cwe-798 + */ + +import semmle.python.security.TaintTracking +import semmle.python.filters.Tests + +class HardcodedValue extends TaintKind { + + HardcodedValue() { + this = "hard coded value" + } + +} + +bindingset[char, fraction] +predicate fewer_characters_than(StrConst str, string char, float fraction) { + exists(string text, int chars | + text = str.getText() and + chars = count(int i | text.charAt(i) = char) | + /* Allow one character */ + chars = 1 or + chars < text.length() * fraction + ) +} + +predicate possible_reflective_name(string name) { + exists(any(ModuleObject m).getAttribute(name)) + or + exists(any(ClassObject c).lookupAttribute(name)) + or + any(ClassObject c).getName() = name + or + any(ModuleObject m).getName() = name + or + exists(builtin_object(name)) +} + +int char_count(StrConst str) { + result = count(string c | c = str.getText().charAt(_)) +} + +predicate capitalized_word(StrConst str) { + str.getText().regexpMatch("[A-Z][a-z]+") +} + +predicate maybeCredential(ControlFlowNode f) { + /* A string that is not too short and unlikely to be text or an identifier. */ + exists(StrConst str | + str = f.getNode() | + /* At least 10 characters */ + str.getText().length() > 9 and + /* Not too much whitespace */ + fewer_characters_than(str, " ", 0.05) and + /* or underscores */ + fewer_characters_than(str, "_", 0.2) and + /* Not too repetitive */ + exists(int chars | + chars = char_count(str) | + chars > 20 or + chars > str.getText().length()/2 + ) and + not possible_reflective_name(str.getText()) and + not capitalized_word(str) + ) + or + /* Or, an integer with at least 8 digits */ + exists(IntegerLiteral lit | + f.getNode() = lit + | + not exists(lit.getValue()) + or + lit.getValue() > 10000000 + ) +} + +class HardcodedValueSource extends TaintSource { + + HardcodedValueSource() { + maybeCredential(this) + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof HardcodedValue + } + +} + +class CredentialSink extends TaintSink { + + CredentialSink() { + exists(string name | + name.regexpMatch(getACredentialRegex()) and + not name.suffix(name.length()-4) = "file" + | + any(FunctionObject func).getNamedArgumentForCall(_, name) = this + or + exists(Keyword k | + k.getArg() = name and k.getValue().getAFlowNode() = this + ) + or + exists(CompareNode cmp, NameNode n | + n.getId() = name + | + cmp.operands(this, any(Eq eq), n) + or + cmp.operands(n, any(Eq eq), this) + ) + ) + } + + + override predicate sinks(TaintKind kind) { + kind instanceof HardcodedValue + } + +} + +/** + * Gets a regular expression for matching names of locations (variables, parameters, keys) that + * indicate the value being held is a credential. + */ +private string getACredentialRegex() { + result = "(?i).*pass(wd|word|code|phrase)(?!.*question).*" or + result = "(?i).*(puid|username|userid).*" or + result = "(?i).*(cert)(?!.*(format|name)).*" +} + +from TaintSource src, TaintSink sink + +where src.flowsToSink(sink) and +not any(TestScope test).contains(src.(ControlFlowNode).getNode()) + +select sink, "Use of hardcoded credentials from $@.", src, src.toString() diff --git a/python/ql/src/Statements/AssertLiteralConstant.py b/python/ql/src/Statements/AssertLiteralConstant.py new file mode 100644 index 000000000000..13271433e44d --- /dev/null +++ b/python/ql/src/Statements/AssertLiteralConstant.py @@ -0,0 +1,9 @@ +def buy_bananas(n): + if n > 500: + assert False, "Too many bananas." + send_order("bananas", n) + +def buy_bananas_correct(n): + if n > 500: + raise AssertionError("Too many bananas") + send_order("bananas", n) diff --git a/python/ql/src/Statements/AssertLiteralConstant.qhelp b/python/ql/src/Statements/AssertLiteralConstant.qhelp new file mode 100644 index 000000000000..056feabb422d --- /dev/null +++ b/python/ql/src/Statements/AssertLiteralConstant.qhelp @@ -0,0 +1,43 @@ + + + +

    + In Python, assertions are not executed when optimizations are enabled. + This may lead to unexpected behavior when assertions are used to the check + validity of a piece of input. +

    + +
    + + +

    + If the value being tested is false, replace the assert + statement with a raise statement that raises an appropriate + exception. If the value being tested is true, delete the assert + statement or replace it with a pass statement. +

    + +
    + +

    + This example shows a function buy_bananas that takes a + number n as input. The function checks that this number is not too big + before sending off an order for that number of bananas. Because this is done + using an assert statement, the check disappears when + optimizations are enabled. The second function corrects this error by + explicitly raising an AssertionError, and checks the value even + when optimizations are enabled. +

    + + + +
    + + +
  • Python Language Reference: The assert statement.
  • +
  • The Python Tutorial: “Compiled†Python files.
  • + +
    +
    diff --git a/python/ql/src/Statements/AssertLiteralConstant.ql b/python/ql/src/Statements/AssertLiteralConstant.ql new file mode 100644 index 000000000000..bf575dd0e25c --- /dev/null +++ b/python/ql/src/Statements/AssertLiteralConstant.ql @@ -0,0 +1,32 @@ +/** + * @name Assert statement tests the truth value of a literal constant + * @description An assert statement testing a literal constant value may exhibit + * different behavior when optimizations are enabled. + * @kind problem + * @tags reliability + * correctness + * @problem.severity recommendation + * @sub-severity low + * @precision medium + * @id py/assert-literal-constant + */ + +import python +import semmle.python.filters.Tests + +from Assert a, string value +where + /* Exclude asserts inside test cases */ + not a.getScope() instanceof Test and + exists(Expr test | test = a.getTest() | + value = test.(IntegerLiteral).getN() + or + value = "\"" + test.(StrConst).getS() + "\"" + or + value = test.(NameConstant).toString() + ) and + /* Exclude asserts appearing at the end of a chain of `elif`s */ + not exists(If i | + i.getElif().getAnOrelse() = a + ) +select a, "Assert of literal constant " + value + "." diff --git a/python/ql/src/Statements/AssertOnTuple.py b/python/ql/src/Statements/AssertOnTuple.py new file mode 100644 index 000000000000..b42d74faf1f3 --- /dev/null +++ b/python/ql/src/Statements/AssertOnTuple.py @@ -0,0 +1,7 @@ +assert xxx and yyy # Alternative 1a. Check both expressions are true + +assert xxx, yyy # Alternative 1b. Check 'xxx' is true, 'yyy' is the failure message. + +tuple = (xxx, yyy) # Alternative 2. Check both elements of the tuple match expectations. +assert tuple[0]==xxx +assert tuple[1]==yyy diff --git a/python/ql/src/Statements/AssertOnTuple.qhelp b/python/ql/src/Statements/AssertOnTuple.qhelp new file mode 100644 index 000000000000..35dca7228990 --- /dev/null +++ b/python/ql/src/Statements/AssertOnTuple.qhelp @@ -0,0 +1,46 @@ + + + +

    When you define an assert statement to test a tuple the test +will either always succeed (if the tuple is non-empty) or always +fail (if the tuple is empty).

    + +

    This error usually occurs when the programmer writes +assert (condition, message) + +instead of the correct form +assert condition, message + +

    + +
    + + +

    Review the code and determine the purpose of the assert statement:

    +
      +
    • +If the "tuple" has been created in error, then remove the parentheses and correct the statement
    • +
    • If validation of a tuple is intended, then you should define an assert statement +for each element of the tuple.
    • +
    + +
    + +

    The statement assert (xxx, yyy) attempts to test a "tuple" (xxx, yyy). +The original intention may be any of the alternatives listed below:

    + + +

    If you want to define a validity check on the values of a tuple then these must be tested +individually.

    + +
    + + +
  • Python Language Reference: The assert statement.
  • +
  • Tutorials Point: Assertions in Python.
  • + + +
    +
    diff --git a/python/ql/src/Statements/AssertOnTuple.ql b/python/ql/src/Statements/AssertOnTuple.ql new file mode 100644 index 000000000000..8ca00f2391e2 --- /dev/null +++ b/python/ql/src/Statements/AssertOnTuple.ql @@ -0,0 +1,24 @@ +/** + * @name Asserting a tuple + * @description Using an assert statement to test a tuple provides no validity checking. + * @kind problem + * @tags reliability + * maintainability + * external/cwe/cwe-670 + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/asserts-tuple + */ + +import python + +from Assert a, string b, string non +where a.getTest() instanceof Tuple and + (if exists(((Tuple)a.getTest()).getAnElt()) then + (b = "True" and non = "non-") + else + (b = "False" and non = "") + ) +select a, "Assertion of " + non + "empty tuple is always " + b + "." + diff --git a/python/ql/src/Statements/BreakOrReturnInFinally.qhelp b/python/ql/src/Statements/BreakOrReturnInFinally.qhelp new file mode 100644 index 000000000000..e759b6e8cb44 --- /dev/null +++ b/python/ql/src/Statements/BreakOrReturnInFinally.qhelp @@ -0,0 +1,32 @@ + + + +

    When a break or return statement is used in a +finally block this causes the try-finally block +to exit immediately discarding the exception. This is unlikely to be the +intention of the developer and makes the code more difficult to read.

    + +
    + + +

    Either move the break or return statement to +immediately after the finally block or use an explicit +except block to handle the exception.

    + +

    These modifications are behavior changing so you must take care to ensure +that the resulting behavior is correct.

    + +
    + + +
  • +Python Language Reference: +The try statement, +The break statement, +The return statement.
  • + + +
    +
    diff --git a/python/ql/src/Statements/BreakOrReturnInFinally.ql b/python/ql/src/Statements/BreakOrReturnInFinally.ql new file mode 100644 index 000000000000..1d9bc7296c9f --- /dev/null +++ b/python/ql/src/Statements/BreakOrReturnInFinally.ql @@ -0,0 +1,27 @@ +/** + * @name 'break' or 'return' statement in finally + * @description Using a Break or Return statement in a finally block causes the + * Try-finally block to exit, discarding the exception. + * @kind problem + * @tags reliability + * maintainability + * external/cwe/cwe-584 + * @problem.severity warning + * @sub-severity low + * @precision medium + * @id py/exit-from-finally + */ + +import python + +from Stmt s, string kind +where +s instanceof Return and kind = "return" and exists(Try t | t.getFinalbody().contains(s)) +or +s instanceof Break and kind = "break" and +exists(Try t | t.getFinalbody().contains(s) | + not exists(For loop | loop.contains(s) and t.getFinalbody().contains(loop)) + and + not exists(While loop | loop.contains(s) and t.getFinalbody().contains(loop)) +) +select s, "'" + kind + "' in a finally block will swallow any exceptions raised." diff --git a/python/ql/src/Statements/C_StyleParentheses.py b/python/ql/src/Statements/C_StyleParentheses.py new file mode 100644 index 000000000000..b3b28316ce2e --- /dev/null +++ b/python/ql/src/Statements/C_StyleParentheses.py @@ -0,0 +1,23 @@ + +#Written in Java or C style +def gcd(a, b): + while(a != 0 and b != 0): + if(a > b): + a = a % b + else: + b = b % a + if(a == 0): + return (b) + return (a) + +#Written in a more Pythonic style +def gcd(a, b): + while a != 0 and b != 0: + if a > b: + a = a % b + else: + b = b % a + if a == 0: + return b + return a + diff --git a/python/ql/src/Statements/C_StyleParentheses.qhelp b/python/ql/src/Statements/C_StyleParentheses.qhelp new file mode 100644 index 000000000000..772e7822895d --- /dev/null +++ b/python/ql/src/Statements/C_StyleParentheses.qhelp @@ -0,0 +1,43 @@ + + + +

    Python is designed to be more readable, at least for Western readers, than languages in the C family. +This is achieved, in part, by using English language keywords and more familiar punctuation. +Top level expressions are thus bracketed by the keyword and either a colon or new line, which can be more +easily picked put by eye than parentheses. +

    + +

    Using superfluous parentheses can impair this readability by making the code harder to scan and parse by eye. +Parentheses often serve as a visual clue for more complex expressions, and adding them unnecessarily can be distracting. +

    + +

    One notable exception to this rule is when an expression has to span multiple lines. In which case, using of parentheses is +preferred to using a back slash for line continuation. +

    + +
    + + +

    +Remove the unnecessary parentheses. +

    + +
    + +

    In the first of the two examples, most of the expressions are wrapped in parentheses. +This is harder to read than the second example, especially to a programmer more familiar with Python than with C or Java. + +

    + +
    + + +
  • Python Language Reference: Full grammar specification.
  • +
  • Google Python Style Guide: Use parentheses sparingly.
  • +
  • Python PEP Index: PEP 8.
  • + + +
    +
    diff --git a/python/ql/src/Statements/C_StyleParentheses.ql b/python/ql/src/Statements/C_StyleParentheses.ql new file mode 100644 index 000000000000..c670876e15a0 --- /dev/null +++ b/python/ql/src/Statements/C_StyleParentheses.ql @@ -0,0 +1,32 @@ +/** + * @name C-style condition + * @description Putting parentheses around a condition in an 'if' or 'while' statement is + * unnecessary and harder to read. + * @kind problem + * @tags maintainability + * @problem.severity recommendation + * @sub-severity low + * @deprecated + * @precision very-high + * @id py/c-style-parentheses + */ + +import python + +from Expr e, Location l, string kind, string what +where e.isParenthesized() and +not e instanceof Tuple and +( + exists(If i | i.getTest() = e) and kind = "if" and what = "condition" + or + exists(While w | w.getTest() = e) and kind = "while" and what = "condition" + or + exists(Return r | r.getValue() = e) and kind = "return" and what = "value" + or + exists(Assert a | a.getTest() = e and not exists(a.getMsg())) and kind = "assert" and what = "test" +) +and +// These require parentheses +(not e instanceof Yield and not e instanceof YieldFrom and not e instanceof GeneratorExp) and +l = e.getLocation() and l.getStartLine() = l.getEndLine() +select e, "Parenthesized " + what + " in '" + kind + "' statement." diff --git a/python/ql/src/Statements/ConstantInConditional.py b/python/ql/src/Statements/ConstantInConditional.py new file mode 100644 index 000000000000..3abdf99836da --- /dev/null +++ b/python/ql/src/Statements/ConstantInConditional.py @@ -0,0 +1,9 @@ +if True: + print "True is true!" + +def limit(l): + if l < -100: + l = -100 + if 1 > 100: + l = 100 + return l diff --git a/python/ql/src/Statements/ConstantInConditional.qhelp b/python/ql/src/Statements/ConstantInConditional.qhelp new file mode 100644 index 000000000000..d388a0b85a9c --- /dev/null +++ b/python/ql/src/Statements/ConstantInConditional.qhelp @@ -0,0 +1,34 @@ + + + +

    Using a constant value as a test in a conditional statement renders the statement pointless as only +one branch will be run regardless of any other factors.

    + +
    + +

    If the conditional statement is required for debugging or similar then use a variable instead. +Otherwise, remove the conditional statement and any associated dead code.

    + +
    + +

    In the first example the if statement will always be executed and therefore can be removed. The +contents of the statement should be kept though.

    + +

    In the second example the statement l = 100 is never executed because 1 > 100 is always false. +However, it is likely that the intention was l > 100 (the number '1' being misread as the letter 'l') +and that the test should be corrected, rather than deleted. + +

    + +
    + + +
  • Python: The If Statement.
  • +
  • Python: The While Statement.
  • +
  • Python: Literals (constant values).
  • + + +
    +
    diff --git a/python/ql/src/Statements/ConstantInConditional.ql b/python/ql/src/Statements/ConstantInConditional.ql new file mode 100644 index 000000000000..06a63cf037aa --- /dev/null +++ b/python/ql/src/Statements/ConstantInConditional.ql @@ -0,0 +1,42 @@ +/** + * @name Constant in conditional expression or statement + * @description The conditional is always true or always false + * @kind problem + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + * external/cwe/cwe-570 + * external/cwe/cwe-571 + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/constant-conditional-expression + */ + +import python + + +predicate is_condition(Expr cond) { + exists(If i | i.getTest() = cond) or + exists(IfExp ie | ie.getTest() = cond) +} + +/* Treat certain unmodified builtins as constants as well. */ +predicate effective_constant(Name cond) { + exists(GlobalVariable var | var = cond.getVariable() and not exists(NameNode f | f.defines(var)) | + var.getId() = "True" or var.getId() = "False" or var.getId() = "NotImplemented" + ) +} + +predicate test_makes_code_unreachable(Expr cond) { + exists(If i | i.getTest() = cond | i.getStmt(0).isUnreachable() or i.getOrelse(0).isUnreachable()) + or + exists(While w | w.getTest() = cond and w.getStmt(0).isUnreachable()) +} + + +from Expr cond +where is_condition(cond) and (cond.isConstant() or effective_constant(cond)) and +/* Ignore cases where test makes code unreachable, as that is handled in different query */ +not test_makes_code_unreachable(cond) +select cond, "Testing a constant will always give the same result." diff --git a/python/ql/src/Statements/DocStrings.py b/python/ql/src/Statements/DocStrings.py new file mode 100644 index 000000000000..370d4fb6bbd6 --- /dev/null +++ b/python/ql/src/Statements/DocStrings.py @@ -0,0 +1,2 @@ +def add(x, y): + return x + y \ No newline at end of file diff --git a/python/ql/src/Statements/DocStrings.qhelp b/python/ql/src/Statements/DocStrings.qhelp new file mode 100644 index 000000000000..1b9938a1fba2 --- /dev/null +++ b/python/ql/src/Statements/DocStrings.qhelp @@ -0,0 +1,39 @@ + + + + + +

    PEP8 mandates that all public modules, classes, functions and methods should have a documentation +string. Ensuring that every public module, class, function and method is documented makes it easier +for other developers to maintain the code. +

    + +
    + + +

    If a module, class, function or method needs to be public then add a documentation string that +describes the +purpose or use of the object (see PEP 257 for guidelines). If the object does not need to be public +then make it "private" by changing its name from xxx to _xxx.

    + +
    + +

    The following simple, public function should be updated to include a documentation string +immediately after the def line.

    + + +

    You might insert the documentation string: """Return the sum of x and y.""" on line 2. + +

    + + +
  • Python PEP 8: Documentation strings.
  • +
  • Python PEP 257: Documentation string conventions +.
  • + + + +
    +
    diff --git a/python/ql/src/Statements/DocStrings.ql b/python/ql/src/Statements/DocStrings.ql new file mode 100644 index 000000000000..4bf458bd22b7 --- /dev/null +++ b/python/ql/src/Statements/DocStrings.ql @@ -0,0 +1,50 @@ +/** + * @name Missing docstring + * @description Omitting documentation strings from public classes, functions or methods + * makes it more difficult for other developers to maintain the code. + * @kind problem + * @tags maintainability + * @problem.severity recommendation + * @sub-severity low + * @precision medium + * @id py/missing-docstring + */ +/* NOTE: precision of 'medium' reflects the lack of precision in the underlying rule. + * Do we care whether a function has a docstring? That often depends on the reader of that docstring. + */ + +import python + +predicate needs_docstring(Scope s) { + s.isPublic() and + ( + not s instanceof Function + or + function_needs_docstring(s) + ) +} + +predicate function_needs_docstring(Function f) { + not exists(FunctionObject fo, FunctionObject base | fo.overrides(base) and fo.getFunction() = f | + not function_needs_docstring(base.getFunction())) and + f.getName() != "lambda" and + (f.getMetrics().getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 + and not exists(PythonPropertyObject p | + p.getGetter().getFunction() = f or + p.getSetter().getFunction() = f + ) +} + +string scope_type(Scope s) { + result = "Module" and s instanceof Module and not ((Module)s).isPackage() + or + result = "Class" and s instanceof Class + or + result = "Function" and s instanceof Function +} + +from Scope s +where needs_docstring(s) and not exists(s.getDocString()) +select s, scope_type(s) + " " + s.getName() + " does not have a docstring" + + diff --git a/python/ql/src/Statements/ExecUsed.py b/python/ql/src/Statements/ExecUsed.py new file mode 100644 index 000000000000..6ea92f1d0b3d --- /dev/null +++ b/python/ql/src/Statements/ExecUsed.py @@ -0,0 +1,3 @@ + +to_execute = get_untrusted_code() +exec to_execute diff --git a/python/ql/src/Statements/ExecUsed.qhelp b/python/ql/src/Statements/ExecUsed.qhelp new file mode 100644 index 000000000000..f7b345815783 --- /dev/null +++ b/python/ql/src/Statements/ExecUsed.qhelp @@ -0,0 +1,30 @@ + + + + +

    Using exec may introduce a security vulnerability into your program unless +you ensure that the data passed to the statement is neutralized. +

    + +
    + +

    Review all uses of the exec statement (Python 2) or function (Python 3). +Where possible, replace the exec statement or function with normal python code. +Alternatively, ensure that all data passed to the statement is neutralized.

    + +
    + +

    In the example, the exec statement is used and may result in executing code from an attacker.

    + + + +
    + + +
  • Python 2.7 Language Reference: The exec statement.
  • +
  • Python 3 Standard Library: exec.
  • + +
    +
    diff --git a/python/ql/src/Statements/ExecUsed.ql b/python/ql/src/Statements/ExecUsed.ql new file mode 100644 index 000000000000..7e6363ae3c81 --- /dev/null +++ b/python/ql/src/Statements/ExecUsed.ql @@ -0,0 +1,27 @@ +/** + * @name 'exec' used + * @description The 'exec' statement or function is used which could cause arbitrary code to be executed. + * @kind problem + * @tags security + * correctness + * @problem.severity error + * @sub-severity high + * @precision low + * @id py/use-of-exec + */ + +import python + +string message() { + result = "The 'exec' statement is used." and major_version() = 2 + or + result = "The 'exec' function is used." and major_version() = 3 +} + +predicate exec_function_call(Call c) { + major_version() = 3 and exists(GlobalVariable exec | exec = ((Name)c.getFunc()).getVariable() and exec.getId() = "exec") +} + +from AstNode exec +where exec_function_call(exec) or exec instanceof Exec +select exec, message() \ No newline at end of file diff --git a/python/ql/src/Statements/ExitUsed.py b/python/ql/src/Statements/ExitUsed.py new file mode 100644 index 000000000000..ae03479497e7 --- /dev/null +++ b/python/ql/src/Statements/ExitUsed.py @@ -0,0 +1,7 @@ + +def main(): + try: + process() + except Exception as ex: + print(ex) + exit(1) diff --git a/python/ql/src/Statements/IterableStringOrSequence.py b/python/ql/src/Statements/IterableStringOrSequence.py new file mode 100644 index 000000000000..7f5eb2959a0c --- /dev/null +++ b/python/ql/src/Statements/IterableStringOrSequence.py @@ -0,0 +1,18 @@ + +#Mistakenly mixed list and string +def greeting(): + if is_global(): + greet = [ "Hello", "World" ] + else: + greet = "Hello" + for word in greet: + print(word) + +#Only use list +def fixed_greeting(): + if is_global(): + greet = [ "Hello", "World" ] + else: + greet = [ "Hello" ] + for word in greet: + print(word) diff --git a/python/ql/src/Statements/IterableStringOrSequence.qhelp b/python/ql/src/Statements/IterableStringOrSequence.qhelp new file mode 100644 index 000000000000..044474de79b0 --- /dev/null +++ b/python/ql/src/Statements/IterableStringOrSequence.qhelp @@ -0,0 +1,47 @@ + + + +

    The for statement is designed to allow you to iterate over the elements of a +sequence or other iterable object. Strings in Python are iterable, and often used as such. +However, they are also often considered, not as sequences of characters, but as atomic entities. +

    + +

    +One source of defects in Python is mistakenly iterating over a non-iterable object such as an integer. +This sort of defect is easily detected as a TypeError will be raised. However, if a string +is mistakenly used as the iterable in a for statement, which also receives other sequences +(such as lists) then the code will iterate over the string one character at a time. +This is probably not what the programmer intended and results in errors that are hard to find. +

    + +
    + + +

    Since this defect usually indicates a logical error, it is not possible to give a general method +for addressing the defect. However, adding a guard that checks that the iterator is not a string +could be worthwhile. +

    + +
    + +

    +In this example, the loop may iterate over "Hello" producing one character per line, +as well as over [ "Hello", "World" ] +It is likely that the programmer forgot to wrap the "Hello" in brackets. +

    + + +
    + + +
  • Python Language Reference: The for statement, + object.__iter__.
  • +
  • Python Standard Library: Iterator types.
  • +
  • Scipy lecture notes: Iterators, +generator expressions and generators.
  • + + +
    +
    diff --git a/python/ql/src/Statements/IterableStringOrSequence.ql b/python/ql/src/Statements/IterableStringOrSequence.ql new file mode 100644 index 000000000000..a44c3ae72867 --- /dev/null +++ b/python/ql/src/Statements/IterableStringOrSequence.ql @@ -0,0 +1,30 @@ +/** + * @name Iterable can be either a string or a sequence + * @description Iteration over either a string or a sequence in the same loop can cause errors that are hard to find. + * @kind problem + * @tags reliability + * maintainability + * non-local + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/iteration-string-and-sequence + */ + +import python + +predicate is_a_string_type(ClassObject seqtype) { + seqtype = theBytesType() and major_version() = 2 + or + seqtype = theUnicodeType() +} + +from For loop, ControlFlowNode iter, Object str, Object seq, ControlFlowNode seq_origin, ClassObject strtype, ClassObject seqtype, ControlFlowNode str_origin +where loop.getIter().getAFlowNode() = iter and +iter.refersTo(str, strtype, str_origin) and +iter.refersTo(seq, seqtype, seq_origin) and +is_a_string_type(strtype) and +seqtype.isIterable() and +not is_a_string_type(seqtype) + +select loop, "Iteration over $@, of class " + seqtype.getName() + ", may also iterate over $@.", seq_origin, "sequence", str_origin, "string" \ No newline at end of file diff --git a/python/ql/src/Statements/MismatchInMultipleAssignment.py b/python/ql/src/Statements/MismatchInMultipleAssignment.py new file mode 100644 index 000000000000..1671149c52c1 --- /dev/null +++ b/python/ql/src/Statements/MismatchInMultipleAssignment.py @@ -0,0 +1,14 @@ +# Fibonacci series 1: +# the sum of two elements defines the next + +a, b = 0, 1, 1 # Assignment fails: accidentally put three values on right +while b < 10: + print b + a, b = b, a+b + +# Fibonacci series 2: +# the sum of two elements defines the next +a, b = 0, 1 # Assignment succeeds: two variables on left and two values on right +while b < 10: + print b + a, b = b, a+b diff --git a/python/ql/src/Statements/MismatchInMultipleAssignment.qhelp b/python/ql/src/Statements/MismatchInMultipleAssignment.qhelp new file mode 100644 index 000000000000..dc577baa97ea --- /dev/null +++ b/python/ql/src/Statements/MismatchInMultipleAssignment.qhelp @@ -0,0 +1,35 @@ + + + + + +

    An assignment statement evaluates a sequence expression and assigns each item of the sequence to +one of the variables on the left. If there is a mismatch between the number of variables on +the left and the values in the sequence on the right of the statement, then an exception is raised +at runtime. +

    + +
    + +

    Ensure that the number of variables on either side of the assignment match.

    + +
    + +

    The following examples show a simple definition of the Fibonacci series. In the first example, +one of the values in the assignment has been duplicated, causing an exception at runtime.

    + + + +
    + + +
  • Python Language Reference: +Assignment statements.
  • +
  • Python Tutorial: +First steps towards programming.
  • + + +
    +
    diff --git a/python/ql/src/Statements/MismatchInMultipleAssignment.ql b/python/ql/src/Statements/MismatchInMultipleAssignment.ql new file mode 100644 index 000000000000..8dee6d5eb5f8 --- /dev/null +++ b/python/ql/src/Statements/MismatchInMultipleAssignment.ql @@ -0,0 +1,58 @@ +/** + * @name Mismatch in multiple assignment + * @description Assigning multiple variables without ensuring that you define a + * value for each variable causes an exception at runtime. + * @kind problem + * @tags reliability + * correctness + * types + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/mismatched-multiple-assignment + */ + +import python + +private int len(ExprList el) { + result = count(el.getAnItem()) +} + +predicate mismatched(Assign a, int lcount, int rcount, Location loc, string sequenceType) { + exists(ExprList l, ExprList r | + (a.getATarget().(Tuple).getElts() = l or + a.getATarget().(List).getElts() = l) + and + ((a.getValue().(Tuple).getElts() = r and sequenceType = "tuple") or + (a.getValue().(List).getElts() = r and sequenceType = "list")) + and + loc = a.getValue().getLocation() and + lcount = len(l) and + rcount = len(r) and + lcount != rcount and + not exists(Starred s | l.getAnItem() = s or r.getAnItem() = s) + ) +} + +predicate mismatched_tuple_rhs(Assign a, int lcount, int rcount, Location loc) { + exists(ExprList l, TupleObject r, AstNode origin | + (a.getATarget().(Tuple).getElts() = l or + a.getATarget().(List).getElts() = l) + and + a.getValue().refersTo(r, origin) and + loc = origin.getLocation() and + lcount = len(l) and + rcount = r.getLength() and + lcount != rcount and + not exists(Starred s | l.getAnItem() = s) + ) +} + + +from Assign a, int lcount, int rcount, Location loc, string sequenceType +where + mismatched(a, lcount, rcount, loc, sequenceType) + or + mismatched_tuple_rhs(a, lcount, rcount, loc) and + sequenceType = "tuple" +select a, "Left hand side of assignment contains " + lcount + " variables, but right hand side is a $@ of length " + rcount + "." , loc, sequenceType diff --git a/python/ql/src/Statements/ModificationOfLocals.py b/python/ql/src/Statements/ModificationOfLocals.py new file mode 100644 index 000000000000..3274d3cbb59a --- /dev/null +++ b/python/ql/src/Statements/ModificationOfLocals.py @@ -0,0 +1,10 @@ + +def modifies_locals_sum(x, y): + locals()['z'] = x + y + #z will not be defined as modifications to locals() do not alter the local variables. + return z + +def fixed_sum(x, y): + z = x + y + return z + diff --git a/python/ql/src/Statements/ModificationOfLocals.qhelp b/python/ql/src/Statements/ModificationOfLocals.qhelp new file mode 100644 index 000000000000..a94508447d96 --- /dev/null +++ b/python/ql/src/Statements/ModificationOfLocals.qhelp @@ -0,0 +1,33 @@ + + + +

    + The dictionary returned by locals() is not a view of the function's locals, but a copy. + Therefore, modification of the dictionary returned from locals() will not modify the local + variables of the function. +

    + + +
    + + +

    If the intention is to modify a local variable, then do so directly. +

    + +
    + +

    In this example, rather than assigning to the variable z directly, the dictionary returned by locals() +is modified. + +

    + +
    + + +
  • Python Language Reference: The for statement.
  • +
  • Python Tutorial: for statements.
  • + +
    +
    diff --git a/python/ql/src/Statements/ModificationOfLocals.ql b/python/ql/src/Statements/ModificationOfLocals.ql new file mode 100644 index 000000000000..c65be7b33660 --- /dev/null +++ b/python/ql/src/Statements/ModificationOfLocals.ql @@ -0,0 +1,43 @@ +/** + * @name Modification of dictionary returned by locals() + * @description Modifications of the dictionary returned by locals() are not propagated to the local variables of a function. + * @kind problem + * @tags reliability + * correctness + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/modification-of-locals + */ + +import python + +Object aFunctionLocalsObject() { + exists(Call c, Name n, GlobalVariable v | + c = result.getOrigin() and + n = c.getFunc() and + n.getVariable() = v and + v.getId() = "locals" and + c.getScope() instanceof FastLocalsFunction + ) +} + + + +predicate modification_of_locals(ControlFlowNode f) { + f.(SubscriptNode).getValue().refersTo(aFunctionLocalsObject()) and (f.isStore() or f.isDelete()) + or + exists(string mname, AttrNode attr | + attr = f.(CallNode).getFunction() and + attr.getObject(mname).refersTo(aFunctionLocalsObject(), _) | + mname = "pop" or + mname = "popitem" or + mname = "update" or + mname = "clear" + ) +} + +from AstNode a, ControlFlowNode f +where modification_of_locals(f) and a = f.getNode() + +select a, "Modification of the locals() dictionary will have no effect on the local variables." diff --git a/python/ql/src/Statements/NestedLoopsSameVariable.py b/python/ql/src/Statements/NestedLoopsSameVariable.py new file mode 100644 index 000000000000..4e1fd333e084 --- /dev/null +++ b/python/ql/src/Statements/NestedLoopsSameVariable.py @@ -0,0 +1,6 @@ + +for var in range(3): + for var in range(3): + pass + print (var) # Prints 2 2 2 not 0 1 2 as might be expected + diff --git a/python/ql/src/Statements/NestedLoopsSameVariable.qhelp b/python/ql/src/Statements/NestedLoopsSameVariable.qhelp new file mode 100644 index 000000000000..e2e1806d2c8f --- /dev/null +++ b/python/ql/src/Statements/NestedLoopsSameVariable.qhelp @@ -0,0 +1,32 @@ + + + +

    + In Python variables have function-wide scope which means that if two variables have the same name in the + same scope, they are in fact one variable. Consequently, nested loops in which the target variables have + the same name in fact share a single variable. Such loops are difficult to understand as the inner loop will + modify the target variable of the outer loop; this may be a typographical error. +

    + + +
    + + +

    Carefully examine the code and check for possible errors, +particularly considering what would happen if the inner or outer variable were renamed. +

    + +
    + + + + + + +
  • Python Language Reference: The for statement.
  • +
  • Python Tutorial: for statements.
  • + +
    +
    diff --git a/python/ql/src/Statements/NestedLoopsSameVariable.ql b/python/ql/src/Statements/NestedLoopsSameVariable.ql new file mode 100644 index 000000000000..6c1ed0f68ffc --- /dev/null +++ b/python/ql/src/Statements/NestedLoopsSameVariable.ql @@ -0,0 +1,29 @@ +/** + * @name Nested loops with same variable + * @description Nested loops in which the target variable is the same for each loop make + * the behavior of the loops difficult to understand. + * @kind problem + * @tags maintainability + * correctness + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/nested-loops-with-same-variable + */ +import python + +predicate loop_variable(For f, Variable v) { + f.getTarget().defines(v) +} + +predicate variableUsedInNestedLoops(For inner, For outer, Variable v) { + /* Only treat loops in body as inner loops. Loops in the else clause are ignored. */ + outer.getBody().contains(inner) and loop_variable(inner, v) and loop_variable(outer, v) + /* Ignore cases where there is no use of the variable or the only use is in the inner loop */ + and exists(Name n | n.uses(v) and outer.contains(n) and not inner.contains(n)) +} + +from For inner, For outer, Variable v +where variableUsedInNestedLoops(inner, outer, v) +select inner, "Nested for statement uses loop variable '" + v.getId() + "' of enclosing $@.", + outer, "for statement" diff --git a/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.py b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.py new file mode 100644 index 000000000000..b40c929374d6 --- /dev/null +++ b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.py @@ -0,0 +1,16 @@ +def largest_elements(l): + for x in l: + maxnum = 0 + for x in x: + maxnum = max(x, maxnum) + # The outer loop variable x has now been overwritten by the inner loop. + print "The largest element in the list", x, "is", maxnum + + +def largest_elements_correct(l): + for x in l: + maxnum = 0 + for y in x: + maxnum = max(y, maxnum) + print "The largest element in the list", x, "is", maxnum + diff --git a/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.qhelp b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.qhelp new file mode 100644 index 000000000000..25b047ef6df0 --- /dev/null +++ b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.qhelp @@ -0,0 +1,42 @@ + + + +

    + In Python variables have function-wide scope which means that if two + variables have the same name in the same scope, they are in fact one + variable. Consequently, nested loops in which the target variables have the + same name in fact share a single variable. Such loops are difficult to + understand as the inner loop will modify the target variable of the outer + loop. This may lead to unexpected behavior if the loop variable is used + after the inner loop has terminated. +

    + +
    + + +

    + Rename the inner loop variable. +

    + +
    + +

    + This example shows a function that processes a sequence of lists of numbers. It + prints out the largest element from each of the lists. In the first version, the + variable x gets overwritten by the inner loop, resulting in the + wrong output. In the second function, the error has been fixed by renaming the + inner loop variable to stop it overwriting the outer loop variable. +

    + + + +
    + + +
  • Python Language Reference: The for statement.
  • +
  • Python Tutorial: for statements.
  • + +
    +
    diff --git a/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql new file mode 100644 index 000000000000..0082f8c3c1a6 --- /dev/null +++ b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql @@ -0,0 +1,36 @@ +/** + * @name Nested loops with same variable reused after inner loop body + * @description Redefining a variable in an inner loop and then using + * the variable in an outer loop causes unexpected behavior. + * @kind problem + * @tags maintainability + * correctness + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/nested-loops-with-same-variable-reused + */ + +import python + +predicate loop_variable_ssa(For f, Variable v, SsaVariable s) { + f.getTarget().getAFlowNode() = s.getDefinition() and v = s.getVariable() +} + +predicate variableUsedInNestedLoops(For inner, For outer, Variable v, Name n) { + /* Ignore cases where there is no use of the variable or the only use is in the inner loop. */ + outer.contains(n) + and not inner.contains(n) + /* Only treat loops in body as inner loops. Loops in the else clause are ignored. */ + and outer.getBody().contains(inner) + and exists(SsaVariable s | + loop_variable_ssa(inner, v, s.getAnUltimateDefinition()) + and loop_variable_ssa(outer, v, _) + and s.getAUse().getNode() = n + ) +} + +from For inner, For outer, Variable v, Name n +where variableUsedInNestedLoops(inner, outer, v, n) +select inner, "Nested for statement $@ loop variable '" + v.getId() + "' of enclosing $@.", n, "uses", + outer, "for statement" \ No newline at end of file diff --git a/python/ql/src/Statements/NonIteratorInForLoop.py b/python/ql/src/Statements/NonIteratorInForLoop.py new file mode 100644 index 000000000000..40de180da3cf --- /dev/null +++ b/python/ql/src/Statements/NonIteratorInForLoop.py @@ -0,0 +1,6 @@ + + +def illegal_for_loop(seq = None): + for x in seq: + print (x) + diff --git a/python/ql/src/Statements/NonIteratorInForLoop.qhelp b/python/ql/src/Statements/NonIteratorInForLoop.qhelp new file mode 100644 index 000000000000..0165db9fea9e --- /dev/null +++ b/python/ql/src/Statements/NonIteratorInForLoop.qhelp @@ -0,0 +1,36 @@ + + + +

    The for statement is designed to allow you to iterate over the elements of a +sequence or other iterable object. If a non-iterable object is used in a for statement +(for var in object:) then a TypeError will be raised. +

    + +
    + + +

    Since this defect usually indicates a logical error, it is not possible to give a general method +for addressing the defect.

    + +
    + +

    +In this example, the loop may attempt to iterate over None, which is not an iterator. +It is likely that the programmer forgot to test for None before the loop. +

    + + +
    + + +
  • Python Language Reference: The for statement, + object.__iter__.
  • +
  • Python Standard Library: Iterator types.
  • +
  • Scipy lecture notes: Iterators, +generator expressions and generators.
  • + + +
    +
    diff --git a/python/ql/src/Statements/NonIteratorInForLoop.ql b/python/ql/src/Statements/NonIteratorInForLoop.ql new file mode 100644 index 000000000000..27d8d47d31fc --- /dev/null +++ b/python/ql/src/Statements/NonIteratorInForLoop.ql @@ -0,0 +1,23 @@ +/** + * @name Non-iterable used in for loop + * @description Using a non-iterable as the object in a 'for' loop causes a TypeError. + * @kind problem + * @tags reliability + * correctness + * types + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/non-iterable-in-for-loop + */ + +import python + +from For loop, ControlFlowNode iter, ClassObject t, ControlFlowNode origin +where loop.getIter().getAFlowNode() = iter and +iter.refersTo(_, t, origin) and +not t.isIterable() and not t.failedInference() and +not t = theNoneType() and +not t.isDescriptorType() + +select loop, "$@ of class '$@' may be used in for-loop.", origin, "Non-iterator", t, t.getName() diff --git a/python/ql/src/Statements/RedundantAssignment.py b/python/ql/src/Statements/RedundantAssignment.py new file mode 100644 index 000000000000..74ebd85a6229 --- /dev/null +++ b/python/ql/src/Statements/RedundantAssignment.py @@ -0,0 +1,5 @@ +class Spam: + + def __init__(self, eggs): + eggs = eggs + diff --git a/python/ql/src/Statements/RedundantAssignment.qhelp b/python/ql/src/Statements/RedundantAssignment.qhelp new file mode 100644 index 000000000000..67cb7eb17d5b --- /dev/null +++ b/python/ql/src/Statements/RedundantAssignment.qhelp @@ -0,0 +1,29 @@ + + + +

    Assigning a variable to itself is redundant and often an indication of a mistake in the code.

    + +
    + +

    Check the assignment carefully for mistakes. If the assignment is truly redundant and not simply +incorrect then remove it.

    + +
    + +

    In this example the programmer clearly intends to assign to self.eggs but made a +mistake.

    + + + +
    + + + +
  • Python Language Reference: +The assignment statement.
  • + + +
    +
    diff --git a/python/ql/src/Statements/RedundantAssignment.ql b/python/ql/src/Statements/RedundantAssignment.ql new file mode 100644 index 000000000000..231f33e88dc3 --- /dev/null +++ b/python/ql/src/Statements/RedundantAssignment.ql @@ -0,0 +1,92 @@ +/** + * @name Redundant assignment + * @description Assigning a variable to itself is useless and very likely indicates an error in the code. + * @kind problem + * @tags reliability + * useless-code + * external/cwe/cwe-563 + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/redundant-assignment + */ + +import python +predicate assignment(AssignStmt a, Expr left, Expr right) +{ + a.getATarget() = left and a.getValue() = right +} + +predicate corresponding(Expr left, Expr right) { + assignment(_, left, right) + or + exists(Attribute la, Attribute ra | + corresponding(la, ra) and + left = la.getObject() and + right = ra.getObject()) +} + +predicate same_value(Expr left, Expr right) { + same_name(left, right) + or + same_attribute(left, right) +} + +predicate maybe_defined_in_outer_scope(Name n) { + exists(SsaVariable v | v.getAUse().getNode() = n | + v.maybeUndefined() + ) +} + +Variable relevant_var(Name n) { + n.getVariable() = result and + (corresponding(n, _) or corresponding(_, n)) +} + +predicate same_name(Name n1, Name n2) { + corresponding(n1, n2) and + relevant_var(n1) = relevant_var(n2) and + not exists(builtin_object(n1.getId())) and + not maybe_defined_in_outer_scope(n2) +} + +ClassObject value_type(Attribute a) { + a.getObject().refersTo(_, result, _) +} + +predicate is_property_access(Attribute a) { + value_type(a).lookupAttribute(a.getName()) instanceof PropertyObject +} + +predicate same_attribute(Attribute a1, Attribute a2) { + corresponding(a1, a2) and a1.getName() = a2.getName() and same_value(a1.getObject(), a2.getObject()) and + exists(value_type(a1)) and not is_property_access(a1) +} + +int pyflakes_commented_line(File file) { + exists(Comment c | c.getText().toLowerCase().matches("%pyflakes%") | + c.getLocation().hasLocationInfo(file.getName(), result, _, _, _) + ) +} + +predicate pyflakes_commented(AssignStmt assignment) { + exists(Location loc | + assignment.getLocation() = loc and + loc.getStartLine() = pyflakes_commented_line(loc.getFile())) +} + +predicate side_effecting_lhs(Attribute lhs) { + exists(ClassObject cls, ClassObject decl | + lhs.getObject().refersTo(_, cls, _) and + decl = cls.getAnImproperSuperType() and + not decl.isBuiltin() | + decl.declaresAttribute("__setattr__") + ) +} + +from AssignStmt a, Expr left, Expr right +where assignment(a, left, right) + and same_value(left, right) + and not pyflakes_commented(a) and + not side_effecting_lhs(left) +select a, "This assignment assigns a variable to itself." diff --git a/python/ql/src/Statements/ShouldUseWithStatement.py b/python/ql/src/Statements/ShouldUseWithStatement.py new file mode 100644 index 000000000000..958905d0b82c --- /dev/null +++ b/python/ql/src/Statements/ShouldUseWithStatement.py @@ -0,0 +1,10 @@ + +f = open("filename") +try: # Method of ensuring file closure + f.write(...) +finally: + f.close() + + +with open("filename") as f: # Simpler method of ensuring file closure + f.write(...) \ No newline at end of file diff --git a/python/ql/src/Statements/ShouldUseWithStatement.qhelp b/python/ql/src/Statements/ShouldUseWithStatement.qhelp new file mode 100644 index 000000000000..e024ecef4061 --- /dev/null +++ b/python/ql/src/Statements/ShouldUseWithStatement.qhelp @@ -0,0 +1,37 @@ + + + + +

    The with statement was introduced by PEP343 to allow standard uses of +try-finally statements to be factored out. Using this simplification makes code easier +to read.

    + +
    + +

    Review the code and determine whether or not the try-finally is used only to ensure +that a resource is closed. If the only purpose is to ensure that a resource is closed, then replace +the try-finally statement with a with statement.

    + +
    + +

    The following code shows examples of different ways of ensuring that a file is always closed, even +when an error is generated. In the second example, the try-finally block is replaced by +a simpler with statement.

    + + + + +
    + + +
  • Python Language Reference: The with +statement.
  • +
  • Python Standard Library: Context manager +.
  • +
  • Python PEP 343: The "with" Statement.
  • + + +
    +
    diff --git a/python/ql/src/Statements/ShouldUseWithStatement.ql b/python/ql/src/Statements/ShouldUseWithStatement.ql new file mode 100644 index 000000000000..06b3b762db0e --- /dev/null +++ b/python/ql/src/Statements/ShouldUseWithStatement.ql @@ -0,0 +1,37 @@ +/** + * @name Should use a 'with' statement + * @description Using a 'try-finally' block to ensure only that a resource is closed makes code more + * difficult to read. + * @kind problem + * @tags maintainability + * readability + * convention + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/should-use-with + */ + +import python + + +predicate calls_close(Call c) { + exists (Attribute a | c.getFunc() = a and a.getName() = "close") +} + +predicate +only_stmt_in_finally(Try t, Call c) { + exists(ExprStmt s | t.getAFinalstmt() = s and s.getValue() = c and strictcount(t.getAFinalstmt()) = 1) +} + +predicate points_to_context_manager(ControlFlowNode f, ClassObject cls) { + cls.isContextManager() and + forex(Object obj | f.refersTo(obj) | f.refersTo(obj, cls, _)) +} + +from Call close, Try t, ClassObject cls +where only_stmt_in_finally(t, close) and calls_close(close) and +exists(ControlFlowNode f | f = close.getFunc().getAFlowNode().(AttrNode).getObject() | + points_to_context_manager(f, cls)) +select close, "Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.", cls, cls.getName() + diff --git a/python/ql/src/Statements/SideEffectInAssert.py b/python/ql/src/Statements/SideEffectInAssert.py new file mode 100644 index 000000000000..4aba0adc8dec --- /dev/null +++ b/python/ql/src/Statements/SideEffectInAssert.py @@ -0,0 +1 @@ +assert(subprocess.call(['run-backup']) == 0) diff --git a/python/ql/src/Statements/SideEffectInAssert.qhelp b/python/ql/src/Statements/SideEffectInAssert.qhelp new file mode 100644 index 000000000000..0f7a3bfa4b38 --- /dev/null +++ b/python/ql/src/Statements/SideEffectInAssert.qhelp @@ -0,0 +1,37 @@ + + + + + +

    All code defined in assert statements is ignored when optimization is +requested, that is, the program is run with the -O flag. +If an assert statement has any side-effects then the behavior of +the program changes when optimization is requested.

    + +
    + + +

    Move all expressions with side-effects out of assert statements.

    + +
    + +

    +In the example, the exit code from subprocess.call() is checked against 0, but the entire +expression is called from within an assert statement. If the code is ever run, then the +not only the assertion itself, but also the external call, will be discarded. It is better to save the result +of subprocess.call() to a temporary variable, and to assert that variable to be 0. +

    + + + +
    + + +
  • Python Language Reference: The assert statement.
  • +
  • TutorialsPoint, Python Programming: Assertions in Python.
  • + + +
    +
    diff --git a/python/ql/src/Statements/SideEffectInAssert.ql b/python/ql/src/Statements/SideEffectInAssert.ql new file mode 100644 index 000000000000..e62685b7c33a --- /dev/null +++ b/python/ql/src/Statements/SideEffectInAssert.ql @@ -0,0 +1,37 @@ +/** + * @name An assert statement has a side-effect + * @description Side-effects in assert statements result in differences between normal + * and optimized behavior. + * @kind problem + * @tags reliability + * maintainability + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/side-effect-in-assert + */ + +import python + +predicate func_with_side_effects(Expr e) { + exists(string name | + name = ((Attribute)e).getName() or name = ((Name)e).getId() | + name = "print" or name = "write" or name = "append" or + name = "pop" or name = "remove" or name = "discard" or + name = "delete" or name = "close" or name = "open" or + name = "exit" + ) +} + +predicate probable_side_effect(Expr e) { + // Only consider explicit yields, not artificial ones in comprehensions + e instanceof Yield and not exists(Comp c | c.contains(e)) + or + e instanceof YieldFrom + or + e instanceof Call and func_with_side_effects(((Call)e).getFunc()) +} + +from Assert a, Expr e +where probable_side_effect(e) and a.contains(e) +select a, "This 'assert' statement contains $@ which may have side effects.", e, "an expression" diff --git a/python/ql/src/Statements/StatementNoEffect.py b/python/ql/src/Statements/StatementNoEffect.py new file mode 100644 index 000000000000..c22c921a1b9c --- /dev/null +++ b/python/ql/src/Statements/StatementNoEffect.py @@ -0,0 +1,4 @@ + +def increment_and_show(x): + ++x + x.show diff --git a/python/ql/src/Statements/StatementNoEffect.qhelp b/python/ql/src/Statements/StatementNoEffect.qhelp new file mode 100644 index 000000000000..0fd1207fa12a --- /dev/null +++ b/python/ql/src/Statements/StatementNoEffect.qhelp @@ -0,0 +1,35 @@ + + + + + +

    An expression statement without side effects is just clutter. It confuses the reader and may have a slight impact on performance. +

    + +
    + +

    First determine what the intention of the code was, if there is no intention of a side effect, then just delete the statement. +However, it is probable that there is a mistake in the code and some effect was intended. +

    +

    +This query will not flag a statement consisting solely of a string as having no side effect, as these are often used as comments. +If you want to use strings as comments, the most common convention is to use triple quoted strings rather than single quoted ones. +Although consistency is more important than conforming to any particular style. +

    + +
    + + +

    In this example neither line of the increment_and_show() function has any effect. +

    +The first line, ++x, has no effect as it applies the unary plus operator twice. Probably the programmer intended x += 1 +

    +

    +The second line, x.show, has no observable effect, but it is likely that x.show() was intended. +

    + + +
    +
    diff --git a/python/ql/src/Statements/StatementNoEffect.ql b/python/ql/src/Statements/StatementNoEffect.ql new file mode 100644 index 000000000000..b0fcd05b3f18 --- /dev/null +++ b/python/ql/src/Statements/StatementNoEffect.ql @@ -0,0 +1,120 @@ +/** + * @name Statement has no effect + * @description A statement has no effect + * @kind problem + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + * @problem.severity recommendation + * @sub-severity high + * @precision high + * @id py/ineffectual-statement + */ + +import python + +predicate understood_attribute(Attribute attr, ClassObject cls, ClassObject attr_cls) { + exists(string name | + attr.getName() = name | + attr.getObject().refersTo(_, cls, _) and + cls.attributeRefersTo(name, _, attr_cls, _) + ) +} + +/* Conservative estimate of whether attribute lookup has a side effect */ +predicate side_effecting_attribute(Attribute attr) { + exists(ClassObject cls, ClassObject attr_cls | + understood_attribute(attr, cls, attr_cls) and + side_effecting_descriptor_type(attr_cls) + ) +} + +predicate maybe_side_effecting_attribute(Attribute attr) { + not understood_attribute(attr, _, _) and not attr.refersTo(_) + or + side_effecting_attribute(attr) +} + +predicate side_effecting_descriptor_type(ClassObject descriptor) { + descriptor.isDescriptorType() and + /* Technically all descriptor gets have side effects, + * but some are indicative of a missing call and + * we want to treat them as having no effect. */ + not descriptor = thePyFunctionType() and + not descriptor = theStaticMethodType() and + not descriptor = theClassMethodType() +} + +/** Side effecting binary operators are rare, so we assume they are not + * side-effecting unless we know otherwise. + */ +predicate side_effecting_binary(Expr b) { + exists(Expr sub, string method_name | + sub = b.(BinaryExpr).getLeft() and + method_name = b.(BinaryExpr).getOp().getSpecialMethodName() + or + exists(Cmpop op | + b.(Compare).compares(sub, op, _) and + method_name = op.getSpecialMethodName() + ) + | + exists(ClassObject cls | + sub.refersTo(_, cls, _) and + cls.hasAttribute(method_name) + and + not exists(ClassObject declaring | + declaring.declaresAttribute(method_name) + and declaring = cls.getAnImproperSuperType() and + declaring.isBuiltin() and not declaring = theObjectType() + ) + ) + ) +} + +predicate is_notebook(File f) { + exists(Comment c | + c.getLocation().getFile() = f | + c.getText().regexpMatch("#\\s*.+\\s*") + ) +} + +/** Expression (statement) in a jupyter/ipython notebook */ +predicate in_notebook(Expr e) { + is_notebook(e.getScope().(Module).getFile()) +} + +FunctionObject assertRaises() { + exists(ModuleObject unittest, ClassObject testcase | + unittest.getName() = "unittest" and + testcase = unittest.getAttribute("TestCase") and + result = testcase.lookupAttribute("assertRaises") + ) +} + +/** Holds if expression `e` is in a `with` block that tests for exceptions being raised. */ +predicate in_raises_test(Expr e) { + exists(With w | + w.contains(e) and + w.getContextExpr() = assertRaises().getACall().getNode() + ) +} + +predicate no_effect(Expr e) { + not e instanceof StrConst and + not ((StrConst)e).isDocString() and + not e.hasSideEffects() and + forall(Expr sub | + sub = e.getASubExpression*() + | + not side_effecting_binary(sub) + and + not maybe_side_effecting_attribute(sub) + ) and + not in_notebook(e) and + not in_raises_test(e) +} + +from ExprStmt stmt +where no_effect(stmt.getValue()) +select stmt, "This statement has no effect." + diff --git a/python/ql/src/Statements/StringConcatenationInLoop.qhelp b/python/ql/src/Statements/StringConcatenationInLoop.qhelp new file mode 100644 index 000000000000..8d8f494ddd05 --- /dev/null +++ b/python/ql/src/Statements/StringConcatenationInLoop.qhelp @@ -0,0 +1,27 @@ + + + +

    If you concatenate strings in a loop then the time taken by the loop is quadratic in the number +of iterations.

    + +
    + + +

    Initialize an empty list before the start of the list. +During the loop append the substrings to the list. +At the end of the loop, convert the list to a string by using ''.join(list).

    + + +
    + + + +
  • Python Standard Library: The str.join method.
  • +
  • Python Frequently Asked Questions: +What is the most efficient way to concatenate many strings together?.
  • + + +
    +
    diff --git a/python/ql/src/Statements/StringConcatenationInLoop.ql b/python/ql/src/Statements/StringConcatenationInLoop.ql new file mode 100644 index 000000000000..5ca79a345a59 --- /dev/null +++ b/python/ql/src/Statements/StringConcatenationInLoop.ql @@ -0,0 +1,29 @@ +/** + * @name String concatenation in loop + * @description Concatenating strings in loops has quadratic performance. + * @kind problem + * @tags efficiency + * maintainability + * @problem.severity recommendation + * @sub-severity low + * @precision low + * @id py/string-concatenation-in-loop + */ + +import python + +predicate string_concat_in_loop(BinaryExpr b) { + b.getOp() instanceof Add + and + exists(SsaVariable d, SsaVariable u, BinaryExprNode add, ClassObject str_type | + add.getNode() = b and d = u.getAnUltimateDefinition() | + d.getDefinition().(DefinitionNode).getValue() = add and u.getAUse() = add.getAnOperand() and + add.getAnOperand().refersTo(_, str_type, _) and + (str_type = theBytesType() or str_type = theUnicodeType()) + ) +} + + +from BinaryExpr b, Stmt s +where string_concat_in_loop(b) and s.getASubExpression() = b +select s, "String concatenation in a loop is quadratic in the number of iterations." diff --git a/python/ql/src/Statements/SysExitUsed.py b/python/ql/src/Statements/SysExitUsed.py new file mode 100644 index 000000000000..679f37624cd6 --- /dev/null +++ b/python/ql/src/Statements/SysExitUsed.py @@ -0,0 +1,8 @@ +import sys + +def main(): + try: + process() + except Exception as ex: + print(ex) + sys.exit(1) diff --git a/python/ql/src/Statements/TopLevelPrint.py b/python/ql/src/Statements/TopLevelPrint.py new file mode 100644 index 000000000000..29d6682f291c --- /dev/null +++ b/python/ql/src/Statements/TopLevelPrint.py @@ -0,0 +1,15 @@ + +try: + import fast_system as system +except ImportError: + print ("Cannot import fast system, falling back on slow system") + import slow_system as system + +#Fixed version +import logging + +try: + import fast_system as system +except ImportError: + logging.info("Cannot import fast system, falling back on slow system") + import slow_system as system diff --git a/python/ql/src/Statements/TopLevelPrint.qhelp b/python/ql/src/Statements/TopLevelPrint.qhelp new file mode 100644 index 000000000000..c615f08df8b4 --- /dev/null +++ b/python/ql/src/Statements/TopLevelPrint.qhelp @@ -0,0 +1,33 @@ + + + + + +

    Using print statements in level scope may result in surprising output at import time. +This in turn means that other code cannot safely import the module in question if the program may only write +real output to standard out. +

    + +
    + + +

    Replace the print statements with calls to some form of logging function or use the warnings module.

    + +
    + +

    In the example, importing the module may cause a message to be printed, which may interfere with the operation of the program.

    + + + +
    + + +
  • Python Language Reference: The print statement.
  • +
  • Python Standard Library: The print function.
  • +
  • Python tutorial: Modules.
  • + + +
    +
    diff --git a/python/ql/src/Statements/TopLevelPrint.ql b/python/ql/src/Statements/TopLevelPrint.ql new file mode 100644 index 000000000000..cc56902cd623 --- /dev/null +++ b/python/ql/src/Statements/TopLevelPrint.ql @@ -0,0 +1,35 @@ +/** + * @name Use of a print statement at module level + * @description Using a print statement at module scope (except when guarded by if __name__ == '__main__') will cause surprising output when the module is imported. + * @kind problem + * @tags reliability + * maintainability + * convention + * @problem.severity recommendation + * @sub-severity high + * @precision high + * @id py/print-during-import + */ + +import python + + +predicate main_eq_name(If i) { + exists(Name n, StrConst m, Compare c | + i.getTest() = c and c.getLeft() = n and + c.getAComparator() = m and + n.getId() = "__name__" and + m.getText() = "__main__" + ) +} + +predicate is_print_stmt(Stmt s) { + s instanceof Print or + exists(ExprStmt e, Call c, Name n | e = s and c = e.getValue() and n = c.getFunc() and n.getId() = "print") +} + +from Stmt p +where is_print_stmt(p) and +exists(ModuleObject m | m.getModule() = p.getScope() and m.getKind() = "module") and +not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p) +select p, "Print statement may execute during import." diff --git a/python/ql/src/Statements/UnnecessaryDelete.py b/python/ql/src/Statements/UnnecessaryDelete.py new file mode 100644 index 000000000000..6ddf2d682101 --- /dev/null +++ b/python/ql/src/Statements/UnnecessaryDelete.py @@ -0,0 +1,4 @@ +def unnecessary_delete(): + x = get_some_object() + do_calculation(x) + del x # This del statement is unnecessary diff --git a/python/ql/src/Statements/UnnecessaryDelete.qhelp b/python/ql/src/Statements/UnnecessaryDelete.qhelp new file mode 100644 index 000000000000..38a6ffe5d64b --- /dev/null +++ b/python/ql/src/Statements/UnnecessaryDelete.qhelp @@ -0,0 +1,34 @@ + + + +

    Passing a local variable to a del statement results in that +variable being removed from the local namespace. When exiting a function all +local variables are deleted, so it is unnecessary to explicitly delete variables +in such cases.

    + +
    + +

    Remove the del statement.

    + +
    + +

    + In the function below, the variable x is assigned a value that + is used for a calculation, and is then explicitly deleted before the + function exits. In this case, the delete statement can be removed without + changing the behavior of the function. +

    + + + +
    + + + + +
  • Python: The 'del' statement.
  • +
  • Python/C API Reference Manual: Reference counts.
  • +
    +
    diff --git a/python/ql/src/Statements/UnnecessaryDelete.ql b/python/ql/src/Statements/UnnecessaryDelete.ql new file mode 100644 index 000000000000..fbe196e9fc1c --- /dev/null +++ b/python/ql/src/Statements/UnnecessaryDelete.ql @@ -0,0 +1,33 @@ +/** + * @name Unnecessary delete statement in function + * @description Using a 'delete' statement to delete a local variable is + * unnecessary, because the variable is deleted automatically when + * the function exits. + * @kind problem + * @tags maintainability + * useless-code + * @problem.severity warning + * @sub-severity low + * @precision high + * @id py/unnecessary-delete + */ + + +import python + +from Delete del, Expr e, Function f +where + f.getLastStatement() = del and + e = del.getATarget() and + f.containsInScope(e) and + not e instanceof Subscript and + not e instanceof Attribute and + not exists(Stmt s | s.(While).contains(del) or s.(For).contains(del)) and + /* False positive: calling `sys.exc_info` within a function results in a + reference cycle,and an explicit call to `del` helps break this cycle. */ + not exists(FunctionObject ex | + ex.hasLongName("sys.exc_info") and + ex.getACall().getScope() = f + ) +select del, "Unnecessary deletion of local variable $@ in function $@.", + e.getLocation(), e.toString(), f.getLocation(), f.getName() \ No newline at end of file diff --git a/python/ql/src/Statements/UnnecessaryElseClause.py b/python/ql/src/Statements/UnnecessaryElseClause.py new file mode 100644 index 000000000000..a5a374d07caa --- /dev/null +++ b/python/ql/src/Statements/UnnecessaryElseClause.py @@ -0,0 +1,21 @@ +def pointless_else(container): + for item in container: + if of_interest(item): + return item + else: + raise NotFoundException() + +def no_else(container): + for item in container: + if of_interest(item): + return item + raise NotFoundException() + +def with_break(container): + for item in container: + if of_interest(item): + found = item + break + else: + raise NotFoundException() + return found diff --git a/python/ql/src/Statements/UnnecessaryElseClause.qhelp b/python/ql/src/Statements/UnnecessaryElseClause.qhelp new file mode 100644 index 000000000000..454fb2a3b138 --- /dev/null +++ b/python/ql/src/Statements/UnnecessaryElseClause.qhelp @@ -0,0 +1,32 @@ + + + +

    The else clause of a loop (either a for or a while statement) executes immediately after the loop terminates normally. +If there is a break statement in the loop body, then the else clause is skipped. +If there is no break statement, then the else clause will always be executed after the loop, unless it exits with a return or raise. +Therefore, if there is no break statement in the loop body then the else clause can be replaced with unindented code.

    + +

    Generally the use of else clauses should be avoided where possible, as they are likely to be misunderstood.

    + +
    + +

    Replace the else clause with unindented code.

    + + +
    + +

    In this example, the pointless_else function contains a redundant else clause. +The else clause can be simplified, as shown in the no_else function, which has the same semantics, but has no else clause. +The third example function, with_break, shows a version where the else clause is necessary, as the break statement skips the else clause. +

    + +
    + + +
  • Python Language Reference: The while statement.
  • +
  • Python Tutorial: Break and continue statements, and else clauses on loops.
  • + +
    +
    diff --git a/python/ql/src/Statements/UnnecessaryElseClause.ql b/python/ql/src/Statements/UnnecessaryElseClause.ql new file mode 100644 index 000000000000..cfb93a7c0b70 --- /dev/null +++ b/python/ql/src/Statements/UnnecessaryElseClause.ql @@ -0,0 +1,22 @@ +/** + * @name Unnecessary 'else' clause in loop + * @description An 'else' clause in a 'for' or 'while' statement that does not contain a 'break' is redundant. + * @kind problem + * @tags maintainability + * useless-code + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/redundant-else + */ + +import python + +from Stmt loop, StmtList body, StmtList clause, string kind +where +(exists(For f | f = loop | clause = f.getOrelse() and body = f.getBody() and kind = "for") + or + exists(While w | w = loop | clause = w.getOrelse() and body = w.getBody() and kind = "while") +) +and not exists(Break b | body.contains(b)) +select loop, "This '" + kind + "' statement has a redundant 'else' as no 'break' is present in the body." diff --git a/python/ql/src/Statements/UnnecessaryPass.qhelp b/python/ql/src/Statements/UnnecessaryPass.qhelp new file mode 100644 index 000000000000..1623ac8714de --- /dev/null +++ b/python/ql/src/Statements/UnnecessaryPass.qhelp @@ -0,0 +1,21 @@ + + + +

    A pass statement is only necessary when it is the only statement in a block (the +list of statements forming part of a compound statement). This is because the purpose of the +pass statement is to allow empty blocks where they would otherwise be syntactically invalid. +If the block already contains other statements then the pass statement is unnecessary.

    + +
    + +

    Remove the pass statement.

    + +
    + + +
  • Python: pass.
  • + +
    +
    diff --git a/python/ql/src/Statements/UnnecessaryPass.ql b/python/ql/src/Statements/UnnecessaryPass.ql new file mode 100644 index 000000000000..d98aa9472365 --- /dev/null +++ b/python/ql/src/Statements/UnnecessaryPass.ql @@ -0,0 +1,33 @@ +/** + * @name Unnecessary pass + * @description Unnecessary 'pass' statement + * @kind problem + * @tags maintainability + * useless-code + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/unnecessary-pass + */ + +import python + +predicate is_doc_string(ExprStmt s) { + s.getValue() instanceof Unicode or s.getValue() instanceof Bytes +} + +predicate has_doc_string(StmtList stmts) { + stmts.getParent() instanceof Scope + and + is_doc_string(stmts.getItem(0)) +} + +from Pass p, StmtList list +where list.getAnItem() = p and +( + strictcount(list.getAnItem()) = 2 and not has_doc_string(list) + or + strictcount(list.getAnItem()) > 2 +) +select p, "Unnecessary 'pass' statement." + diff --git a/python/ql/src/Statements/UnreachableCode.py b/python/ql/src/Statements/UnreachableCode.py new file mode 100644 index 000000000000..e3a4fbd47c29 --- /dev/null +++ b/python/ql/src/Statements/UnreachableCode.py @@ -0,0 +1,5 @@ +import math + +def my_div(x, y): + return math.floor(x / y) + remainder = x - math.floor(x / y) * y diff --git a/python/ql/src/Statements/UnreachableCode.qhelp b/python/ql/src/Statements/UnreachableCode.qhelp new file mode 100644 index 000000000000..38fc0de58215 --- /dev/null +++ b/python/ql/src/Statements/UnreachableCode.qhelp @@ -0,0 +1,27 @@ + + + +

    Unreachable code makes the code more difficult to understand and may slow down loading of modules.

    + +
    + +

    Deleting the unreachable code will make the code clearer and preserve the meaning of the code. +However, it is possible that the original intention was that the code should execute and that it is +unreachable signifies some other error.

    + +
    + +

    In this example the assignment to remainder is never reached because there is a +return statement on the previous line.

    + + + +
    + + +
  • Wikipedia: Unreachable Code.
  • + +
    +
    diff --git a/python/ql/src/Statements/UnreachableCode.ql b/python/ql/src/Statements/UnreachableCode.ql new file mode 100644 index 000000000000..8fa8cb7f9e00 --- /dev/null +++ b/python/ql/src/Statements/UnreachableCode.ql @@ -0,0 +1,49 @@ +/** + * @name Unreachable code + * @description Code is unreachable + * @kind problem + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/unreachable-statement + */ + +import python + +predicate typing_import(ImportingStmt is) { + exists(Module m | + is.getScope() = m and + exists(TypeHintComment tc | + tc.getLocation().getFile() = m.getFile() + ) + ) +} + +/** Holds if `s` contains the only `yield` in scope */ +predicate unique_yield(Stmt s) { + exists(Yield y | s.contains(y)) and + exists(Function f | + f = s.getScope() and + strictcount(Yield y | f.containsInScope(y)) = 1 + ) +} + +predicate reportable_unreachable(Stmt s) { + s.isUnreachable() and + not typing_import(s) and + not exists(Stmt other | other.isUnreachable() | + other.contains(s) + or + exists(StmtList l, int i, int j | + l.getItem(i) = other and l.getItem(j) = s and i < j + ) + ) and + not unique_yield(s) +} + +from Stmt s +where reportable_unreachable(s) +select s, "Unreachable statement." diff --git a/python/ql/src/Statements/UnusedExceptionObject.py b/python/ql/src/Statements/UnusedExceptionObject.py new file mode 100644 index 000000000000..3fc8a456bd3f --- /dev/null +++ b/python/ql/src/Statements/UnusedExceptionObject.py @@ -0,0 +1,16 @@ + +def do_action_forgotten_raise(action): + if action == "go": + start() + elif action == "stop": + stop() + else: + ValueError(action) + +def do_action(action): + if action == "go": + start() + elif action == "stop": + stop() + else: + raise ValueError(action) diff --git a/python/ql/src/Statements/UnusedExceptionObject.qhelp b/python/ql/src/Statements/UnusedExceptionObject.qhelp new file mode 100644 index 000000000000..63fca0cf8a46 --- /dev/null +++ b/python/ql/src/Statements/UnusedExceptionObject.qhelp @@ -0,0 +1,25 @@ + + + + + +

    Creating a new exception object is no different from creating any other object. The exception needs to be raised to have an effect. +

    + +
    + +

    Insert a raise before the exception. +

    + +
    + + +

    In this example, the first function do_action_forgotten_raise() silently ignores any erroneous input. +Whereas, the second function do_action correctly raises an exception if the 'action' is not understood. +

    + + +
    +
    diff --git a/python/ql/src/Statements/UnusedExceptionObject.ql b/python/ql/src/Statements/UnusedExceptionObject.ql new file mode 100644 index 000000000000..be848ad69c3b --- /dev/null +++ b/python/ql/src/Statements/UnusedExceptionObject.ql @@ -0,0 +1,19 @@ +/** + * @name Unused exception object + * @description An exception object is created, but is not used. + * @kind problem + * @tags reliability + * maintainability + * @problem.severity error + * @sub-severity low + * @precision very-high + * @id py/unused-exception-object + */ + +import python + +from Call call, ClassObject ex +where call.getFunc().refersTo(ex) and ex.getAnImproperSuperType() = theExceptionType() +and exists(ExprStmt s | s.getValue() = call) + +select call, "Instantiating an exception, but not raising it, has no effect" diff --git a/python/ql/src/Statements/UseOfExit.qhelp b/python/ql/src/Statements/UseOfExit.qhelp new file mode 100644 index 000000000000..004461513e76 --- /dev/null +++ b/python/ql/src/Statements/UseOfExit.qhelp @@ -0,0 +1,35 @@ + + + + +

    The exit and quit "functions" are actually site.Quitter objects and +are loaded, at interpreter start up, from site.py. +However, if the interpreter is started with the -S flag, or a custom site.py +is used then exit and quit may not be present. +

    + +
    + +

    Replace uses of exit() and quit() with sys.exit() which is +built into the interpreter and is guaranteed to be present.

    + +
    + +

    In this example, exit() is used and will fail if the interpreter is passed the -S option.

    + + + +

    In this example, sys.exit() is used and will behave the same regardless of the interpreter options.

    + + + +
    + + +
  • Python Documentation: Command line and environment.
  • +
  • Python Documentation: Site-specific configuration hook.
  • + +
    +
    diff --git a/python/ql/src/Statements/UseOfExit.ql b/python/ql/src/Statements/UseOfExit.ql new file mode 100644 index 000000000000..3c21be6b1de1 --- /dev/null +++ b/python/ql/src/Statements/UseOfExit.ql @@ -0,0 +1,16 @@ +/** + * @name Use of exit() or quit() + * @description exit() or quit() may fail if the interpreter is run with the -S option. + * @kind problem + * @tags maintainability + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/use-of-exit-or-quit + */ + +import python + +from CallNode call, string name +where call.getFunction().refersTo(quitterObject(name)) +select call, "The '" + name + "' site.Quitter object may not exist if the 'site' module is not loaded or is modified." diff --git a/python/ql/src/Testing/ImpreciseAssert.py b/python/ql/src/Testing/ImpreciseAssert.py new file mode 100644 index 000000000000..08e083ff0bbe --- /dev/null +++ b/python/ql/src/Testing/ImpreciseAssert.py @@ -0,0 +1,9 @@ +from unittest import TestCase + +class MyTest(TestCase): + + + def testInts(self): + self.assertTrue(1 == 1) + self.assertFalse(1 > 2) + self.assertTrue(1 in []) #This will fail diff --git a/python/ql/src/Testing/ImpreciseAssert.qhelp b/python/ql/src/Testing/ImpreciseAssert.qhelp new file mode 100644 index 000000000000..2cf9aa3291c6 --- /dev/null +++ b/python/ql/src/Testing/ImpreciseAssert.qhelp @@ -0,0 +1,37 @@ + + + + + +

    The class unittest.TestCase provides a range of assertion methods. As well as the general forms assertTrue() and assertFalse() +more specific forms such as assertGreaterEquals() and assertNotIn() are provided. +By using the more specific forms it is possible to get more precise and informative failure messages in the event of a test failing. This can speed up the debugging process. +

    +
    + + +

    Replace all calls to assertTrue() and assertFalse() that do not provide a custom failure message with a more specific variant. +Alternatively, provide a tailored failure message using the assertTrue(condition, message) form. +

    +
    + + +

    In this example, assertTrue() and assertFalse() are used.

    + +

    +This will make it more difficult to determine what has gone wrong when self.assertTrue(1 in []) fails. +The failure message "AssertionError: False is not true" is not very helpful. +

    + +

    A more useful error message can be generated by changing the asserts to the more specific forms as in the following example.

    + +

    In this case, the failure message "AssertionError: 1 not found in []" is much more informative.

    +
    + + +
  • Python library reference: TestCase.assertEqual.
  • +
    + +
    diff --git a/python/ql/src/Testing/ImpreciseAssert.ql b/python/ql/src/Testing/ImpreciseAssert.ql new file mode 100644 index 000000000000..589d1c045d51 --- /dev/null +++ b/python/ql/src/Testing/ImpreciseAssert.ql @@ -0,0 +1,101 @@ +/** + * @name Imprecise assert + * @description Using 'assertTrue' or 'assertFalse' rather than a more specific assertion can give uninformative failure messages. + * @kind problem + * @tags maintainability + * testability + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/imprecise-assert + */ + +import python + +/* Helper predicate for CallToAssertOnComparison class */ +predicate callToAssertOnComparison(Call call, string assertName, Cmpop op) { + call.getFunc().(Attribute).getName() = assertName + and + (assertName = "assertTrue" or assertName = "assertFalse") + and + exists(Compare cmp | + cmp = call.getArg(0) and + /* Exclude complex comparisons like: a < b < c */ + not exists(cmp.getOp(1)) and + op = cmp.getOp(0) + ) +} + +class CallToAssertOnComparison extends Call { + + CallToAssertOnComparison() { + callToAssertOnComparison(this, _, _) + } + + Cmpop getOperator() { + callToAssertOnComparison(this, _, result) + } + + string getMethodName() { + callToAssertOnComparison(this, result, _) + } + + string getBetterName() { + exists(Cmpop op | + callToAssertOnComparison(this, "assertTrue", op) and + ( + op instanceof Eq and result = "assertEqual" + or + op instanceof NotEq and result = "assertNotEqual" + or + op instanceof Lt and result = "assertLess" + or + op instanceof LtE and result = "assertLessEqual" + or + op instanceof Gt and result = "assertGreater" + or + op instanceof GtE and result = "assertGreaterEqual" + or + op instanceof In and result = "assertIn" + or + op instanceof NotIn and result = "assertNotIn" + or + op instanceof Is and result = "assertIs" + or + op instanceof IsNot and result = "assertIsNot" + ) + or + callToAssertOnComparison(this, "assertFalse", op) and + ( + op instanceof NotEq and result = "assertEqual" + or + op instanceof Eq and result = "assertNotEqual" + or + op instanceof GtE and result = "assertLess" + or + op instanceof Gt and result = "assertLessEqual" + or + op instanceof LtE and result = "assertGreater" + or + op instanceof Lt and result = "assertGreaterEqual" + or + op instanceof NotIn and result = "assertIn" + or + op instanceof In and result = "assertNotIn" + or + op instanceof IsNot and result = "assertIs" + or + op instanceof Is and result = "assertIsNot" + ) + ) + } + +} + + +from CallToAssertOnComparison call +where + /* Exclude cases where an explicit message is provided*/ + not exists(call.getArg(1)) +select call, call.getMethodName() + "(a " + call.getOperator().getSymbol() + " b) " + + "cannot provide an informative message. Using " + call.getBetterName() + "(a, b) instead will give more informative messages." diff --git a/python/ql/src/Testing/ImpreciseAssert2.py b/python/ql/src/Testing/ImpreciseAssert2.py new file mode 100644 index 000000000000..a2b250b18d35 --- /dev/null +++ b/python/ql/src/Testing/ImpreciseAssert2.py @@ -0,0 +1,9 @@ +from unittest import TestCase + +class MyTest(TestCase): + + + def testInts(self): + self.assertEqual(1, 1) + self.assertLessEqual(1, 2) + self.assertIn(1, []) #This will fail diff --git a/python/ql/src/Testing/Mox.qll b/python/ql/src/Testing/Mox.qll new file mode 100644 index 000000000000..273193298a27 --- /dev/null +++ b/python/ql/src/Testing/Mox.qll @@ -0,0 +1,18 @@ +import python + +/** Whether `mox` or `.StubOutWithMock()` is used in thin module `m`. + */ +predicate useOfMoxInModule(Module m) { + exists(ModuleObject mox | + mox.getName() = "mox" or mox.getName() = "mox3.mox" | + exists(ControlFlowNode use | + use.refersTo(mox) and + use.getScope().getEnclosingModule() = m + ) + ) + or + exists(Call call| + call.getFunc().(Attribute).getName() = "StubOutWithMock" and + call.getEnclosingModule() = m + ) +} diff --git a/python/ql/src/Variables/Definition.qll b/python/ql/src/Variables/Definition.qll new file mode 100644 index 000000000000..0f0fc7f730b0 --- /dev/null +++ b/python/ql/src/Variables/Definition.qll @@ -0,0 +1,160 @@ +import python + + +/** + * A control-flow node that defines a variable + */ +class Definition extends NameNode, DefinitionNode { + + /** + * The variable defined by this control-flow node. + */ + Variable getVariable() { + this.defines(result) + } + + /** + * The SSA variable corresponding to the current definition. Since SSA variables + * are only generated for definitions with at least one use, not all definitions + * will have an SSA variable. + */ + SsaVariable getSsaVariable() { + result.getDefinition() = this + } + + /** + * The index of this definition in its basic block. + */ + private int indexInBB(BasicBlock bb, Variable v) { + v = this.getVariable() and + this = bb.getNode(result) + } + + /** + * The rank of this definition among other definitions of the same variable + * in its basic block. The first definition will have rank 1, and subsequent + * definitions will have sequentially increasing ranks. + */ + private int rankInBB(BasicBlock bb, Variable v) { + exists(int defIdx | defIdx = this.indexInBB(bb, v) | + defIdx = rank[result](int idx, Definition def | idx = def.indexInBB(bb, v) | idx) + ) + } + + /** Is this definition the first in its basic block for its variable? */ + predicate isFirst() { + this.rankInBB(_, _) = 1 + } + + /** Is this definition the last in its basic block for its variable? */ + predicate isLast() { + exists(BasicBlock b, Variable v | + this.rankInBB(b, v) = max(Definition other | any() | other.rankInBB(b, v)) + ) + } + + /** + * Is this definition unused? A definition is unused if the value it provides + * is not read anywhere. + */ + predicate isUnused() { + // SSA variables only exist for definitions that have at least one use. + not exists(this.getSsaVariable()) and + // If a variable is used in a foreign scope, all bets are off. + not this.getVariable().escapes() and + // Global variables don't have SSA variables unless the scope is global. + this.getVariable().getScope() = this.getScope() and + // A call to locals() or vars() in the variable scope counts as a use + not exists(Function f, Call c, string locals_or_vars | + c.getScope() = f and this.getScope() = f and + c.getFunc().(Name).getId() = locals_or_vars | + locals_or_vars = "locals" or locals_or_vars = "vars" + ) + } + + /** + * An immediate re-definition of this definition's variable. + */ + Definition getARedef() { + result != this and + exists(Variable var | var = this.getVariable() and var = result.getVariable() | + // Definitions in different basic blocks. + this.isLast() and + reaches_without_redef(var, this.getBasicBlock(), result.getBasicBlock()) and + result.isFirst() + ) + or + // Definitions in the same basic block. + exists(BasicBlock common, Variable var | + this.rankInBB(common, var) + 1 = result.rankInBB(common, var) + ) + } + + /** + * We only consider assignments as potential alert targets, not parameters + * and imports and other name-defining constructs. + * We also ignore anything named "_", "empty", "unused" or "dummy" + */ + predicate isRelevant() { + exists(AstNode p | + p = this.getNode().getParentNode() | + p instanceof Assign or p instanceof AugAssign or p instanceof Tuple + ) + and + not name_acceptable_for_unused_variable(this.getVariable()) + and + /* Decorated classes and functions are used */ + not exists(this.getNode().getParentNode().(FunctionDef).getDefinedFunction().getADecorator()) + and + not exists(this.getNode().getParentNode().(ClassDef).getDefinedClass().getADecorator()) + } + +} + +/** + * Check whether basic block `a` reaches basic block `b` without an intervening + * definition of variable `v`. The relation is not transitive by default, so any + * observed transitivity will be caused by loops in the control-flow graph. + */ +private +predicate reaches_without_redef(Variable v, BasicBlock a, BasicBlock b) { + exists(Definition def | a.getASuccessor() = b | + def.getBasicBlock() = a and def.getVariable() = v and maybe_redefined(v) + ) or + exists(BasicBlock mid | reaches_without_redef(v, a, mid) | + not exists(NameNode cfn | cfn.defines(v) | + cfn.getBasicBlock() = mid + ) and + mid.getASuccessor() = b + ) +} + +private predicate maybe_redefined(Variable v) { + strictcount(Definition d | d.defines(v)) > 1 +} + +predicate name_acceptable_for_unused_variable(Variable var) { + exists(string name | + var.getId() = name | + name.regexpMatch("_+") or name = "empty" or + name.matches("%unused%") or name = "dummy" or + name.regexpMatch("__.*") + ) +} + + +class ListComprehensionDeclaration extends ListComp { + + Name getALeakedVariableUse() { + major_version() = 2 and + this.getIterationVariable(_).getId() = result.getId() and + result.getScope() = this.getScope() and + this.getAFlowNode().strictlyReaches(result.getAFlowNode()) and + result.isUse() + } + + Name getDefinition() { + result = this.getIterationVariable(0).getAStore() + } + +} diff --git a/python/ql/src/Variables/Global.qhelp b/python/ql/src/Variables/Global.qhelp new file mode 100644 index 000000000000..31df579a6de1 --- /dev/null +++ b/python/ql/src/Variables/Global.qhelp @@ -0,0 +1,20 @@ + + + +

    The use of the global keyword enables functions to modify variables outside of their scope. +These functions may then include side effects that may not be apparent to users +of that function, making the code harder to understand.

    +
    + + +

    Remove the global statement, if possible.

    +
    + + + +
  • Python Language Reference: The global statement.
  • + +
    +
    diff --git a/python/ql/src/Variables/Global.ql b/python/ql/src/Variables/Global.ql new file mode 100644 index 000000000000..8adbd06bcf51 --- /dev/null +++ b/python/ql/src/Variables/Global.ql @@ -0,0 +1,18 @@ +/** + * @name Use of the 'global' statement. + * @description Use of the 'global' statement may indicate poor modularity. + * @kind problem + * @problem.severity recommendation + * @sub-severity low + * @deprecated + * @precision very-high + * @id py/use-of-global + */ + +import python + +from Global g +where not g.getScope() instanceof Module +select g, "Updating global variables except at module initialization is discouraged" + + diff --git a/python/ql/src/Variables/GlobalAtModuleLevel.qhelp b/python/ql/src/Variables/GlobalAtModuleLevel.qhelp new file mode 100644 index 000000000000..a0c1c7e673b7 --- /dev/null +++ b/python/ql/src/Variables/GlobalAtModuleLevel.qhelp @@ -0,0 +1,20 @@ + + + +

    The global statement is used to specify that assignments to that name are assignments to the +variable in the global (module) scope, rather than in the local scope. +At the module level, this statement is redundant because the local scope and global scope are the same.

    + +
    + +

    Remove the global statement.

    + +
    + + +
  • Python Language Reference: The global statement.
  • + +
    +
    diff --git a/python/ql/src/Variables/GlobalAtModuleLevel.ql b/python/ql/src/Variables/GlobalAtModuleLevel.ql new file mode 100644 index 000000000000..f3dc9e21440a --- /dev/null +++ b/python/ql/src/Variables/GlobalAtModuleLevel.ql @@ -0,0 +1,17 @@ +/** + * @name Use of 'global' at module level + * @description Use of the 'global' statement at module level + * @kind problem + * @tags maintainability + * useless-code + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/redundant-global-declaration + */ + +import python + +from Global g +where g.getScope() instanceof Module +select g, "Declaring '" + g.getAName() + "' as global at module-level is redundant." \ No newline at end of file diff --git a/python/ql/src/Variables/LeakingListComprehension.py b/python/ql/src/Variables/LeakingListComprehension.py new file mode 100644 index 000000000000..11b876016bd1 --- /dev/null +++ b/python/ql/src/Variables/LeakingListComprehension.py @@ -0,0 +1,7 @@ + +def two_or_three(): + x = 3 + [0 for x in range(3)] + return x # Will return 2 in Python 2 and 3 in Python 3. + +print(two_or_three()) \ No newline at end of file diff --git a/python/ql/src/Variables/LeakingListComprehension.qhelp b/python/ql/src/Variables/LeakingListComprehension.qhelp new file mode 100644 index 000000000000..17b5a0976252 --- /dev/null +++ b/python/ql/src/Variables/LeakingListComprehension.qhelp @@ -0,0 +1,41 @@ + + + + + +

    In Python 2 list comprehensions are evaluated in the enclosing scope, which means that the iteration variable of a list comprehension is visible +outside of the list comprehension. In Python 3 the iteration variable is no longer visible in the enclosing scope. +

    + +

    +Code that uses the value of a list comprehension iteration variable after the list comprehension has finished will +behave differently under Python 2 and Python 3. +

    + +
    + +

    Explicitly set the variable in the outer scope to the value that it would have held when run under Python 2. +Then rename the list comprehension variable for additional clarity. +

    + +
    + +

    In this example, x is initially assigned the value of 3. +In Python 3, x will be unchanged as the list comprehension is evaluated in its own scope. +In Python 2, evaluation of the list comprehension occurs in the scope of two_or_three, setting x to 2.

    + + +

    The following example is the same code as above, but the list comprehension variable is renamed to ensure it does not overwrite x.

    + + +
    + + +
  • Python Tutorial: List Comprehensions.
  • +
  • The History of Python: From List Comprehensions to Generator Expressions.
  • +
  • Python Language Reference: List displays.
  • + +
    +
    diff --git a/python/ql/src/Variables/LeakingListComprehension.ql b/python/ql/src/Variables/LeakingListComprehension.ql new file mode 100644 index 000000000000..efec82af4adc --- /dev/null +++ b/python/ql/src/Variables/LeakingListComprehension.ql @@ -0,0 +1,30 @@ +/** + * @name List comprehension variable used in enclosing scope + * @description Using the iteration variable of a list comprehension in the enclosing scope will result in different behavior between Python 2 and 3 and is confusing. + * @kind problem + * @tags portability + * correctness + * @problem.severity warning + * @sub-severity high + * @precision very-high + * @id py/leaking-list-comprehension + */ + +import python +import Definition + +from ListComprehensionDeclaration l, Name use, Name defn +where + use = l.getALeakedVariableUse() and + defn = l.getDefinition() and + l.getAFlowNode().strictlyReaches(use.getAFlowNode()) and + /* Make sure we aren't in a loop, as the variable may be redefined */ + not use.getAFlowNode().strictlyReaches(l.getAFlowNode()) and + not l.contains(use) and + not use.deletes(_) and + not exists(SsaVariable v | + v.getAUse() = use.getAFlowNode() and + not v.getDefinition().strictlyDominates(l.getAFlowNode()) + ) + +select use, use.getId() + " may have a different value in Python 3, as the $@ will not be in scope.", defn, "list comprehension variable" diff --git a/python/ql/src/Variables/LeakingListComprehensionFixed.py b/python/ql/src/Variables/LeakingListComprehensionFixed.py new file mode 100644 index 000000000000..e9cd52363be4 --- /dev/null +++ b/python/ql/src/Variables/LeakingListComprehensionFixed.py @@ -0,0 +1,7 @@ + +def just_three(): + x = 3 + [0 for y in range(3)] + return x # Will return always return 3. + +print(just_three()) \ No newline at end of file diff --git a/python/ql/src/Variables/Loop.qll b/python/ql/src/Variables/Loop.qll new file mode 100644 index 000000000000..f3b105463ac1 --- /dev/null +++ b/python/ql/src/Variables/Loop.qll @@ -0,0 +1,38 @@ +import python + + +private predicate empty_sequence(Expr e) { + exists(SsaVariable var | var.getAUse().getNode() = e | empty_sequence(var.getDefinition().getNode())) or + e instanceof List and not exists(e.(List).getAnElt()) or + e instanceof Tuple and not exists(e.(Tuple).getAnElt()) or + e.(StrConst).getText().length() = 0 +} + +/* This has the potential for refinement, but we err on the side of fewer false positives for now. */ +private predicate probably_non_empty_sequence(Expr e) { + not empty_sequence(e) +} + +/** A loop which probably defines v */ +private Stmt loop_probably_defines(Variable v) { + exists(Name defn | defn.defines(v) and result.contains(defn) | + probably_non_empty_sequence(result.(For).getIter()) + or + probably_non_empty_sequence(result.(While).getTest()) + ) +} + +/** Holds if the variable used by `use` is probably defined in a loop */ +predicate probably_defined_in_loop(Name use) { + exists(Stmt loop | + loop = loop_probably_defines(use.getVariable()) | + loop.getAFlowNode().strictlyReaches(use.getAFlowNode()) + ) +} + +/** Holds if `s` is a loop that probably executes at least once */ +predicate loop_probably_executes_at_least_once(Stmt s) { + probably_non_empty_sequence(s.(For).getIter()) + or + probably_non_empty_sequence(s.(While).getTest()) +} diff --git a/python/ql/src/Variables/LoopVariableCapture.py b/python/ql/src/Variables/LoopVariableCapture.py new file mode 100644 index 000000000000..4a6abcb88946 --- /dev/null +++ b/python/ql/src/Variables/LoopVariableCapture.py @@ -0,0 +1,18 @@ + +#Make a list of functions to increment their arguments by 0 to 9. +def make_incrementers(): + result = [] + for i in range(10): + def incrementer(x): + return x + i + result.append(incrementer) + return result + +#This will fail +def test(): + incs = make_incrementers() + for x in range(10): + for y in range(10): + assert incs[x](y) == x+y + +test() \ No newline at end of file diff --git a/python/ql/src/Variables/LoopVariableCapture.qhelp b/python/ql/src/Variables/LoopVariableCapture.qhelp new file mode 100644 index 000000000000..15f2b185eb9d --- /dev/null +++ b/python/ql/src/Variables/LoopVariableCapture.qhelp @@ -0,0 +1,60 @@ + + + + +

    +Nested functions are a useful feature of Python as it allows a function to access the variables of its enclosing function. +However, the programmer needs to be aware that when an inner function accesses a variable in an outer scope, +it is the variable that is captured, not the value of that variable. +

    +

    +Therefore, care must be taken when the captured variable is a loop variable, since it is the loop variable and +not the value of that variable that is captured. +This will mean that by the time that the inner function executes, +the loop variable will have its final value, not the value when the inner function was created. +

    + +
    + +

    +The simplest way to fix this problem is to add a local variable of the same name as the outer variable and initialize that +using the outer variable as a default. + +for var in seq: + ... + def inner_func(arg): + ... + use(var) + +becomes + +for var in seq: + ... + def inner_func(arg, var=var): + ... + use(var) + +

    + +
    + +

    +In this example, a list of functions is created which should each increment its argument by its index in the list. +However, since i will be 9 when the functions execute, they will each increment their argument by 9. +

    + +

    +This can be fixed by adding the default value as shown below. The default value is computed when the function is created, so the desired effect is achieved. +

    + + + +
    + +
  • The Hitchhiker’s Guide to Python: Late Binding Closures
  • +
  • Python Language Reference: Naming and binding
  • + +
    +
    diff --git a/python/ql/src/Variables/LoopVariableCapture.ql b/python/ql/src/Variables/LoopVariableCapture.ql new file mode 100644 index 000000000000..307da04861d0 --- /dev/null +++ b/python/ql/src/Variables/LoopVariableCapture.ql @@ -0,0 +1,47 @@ +/** + * @name Loop variable capture + * @description Capture of a loop variable is not the same as capturing the value of a loop variable, and may be erroneous. + * @kind problem + * @tags correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/loop-variable-capture + */ + +import python + +// Gets the scope of the iteration variable of the looping scope +Scope iteration_variable_scope(AstNode loop) { + result = loop.(For).getScope() + or + result = loop.(Comp).getFunction() +} + +predicate capturing_looping_construct(CallableExpr capturing, AstNode loop, Variable var) { + var.getScope() = iteration_variable_scope(loop) and + var.getAnAccess().getScope() = capturing.getInnerScope() and + capturing.getParentNode+() = loop and + ( + loop.(For).getTarget() = var.getAnAccess() + or + var = loop.(Comp).getAnIterationVariable() + ) +} + +predicate escaping_capturing_looping_construct(CallableExpr capturing, AstNode loop, Variable var) { + capturing_looping_construct(capturing, loop, var) + and + // Escapes if used out side of for loop or is a lambda in a comprehension + ( + exists(Expr e, For forloop | forloop = loop and e.refersTo(_, _, capturing) | not forloop.contains(e)) + or + loop.(Comp).getElt() = capturing + or + loop.(Comp).getElt().(Tuple).getAnElt() = capturing + ) +} + +from CallableExpr capturing, AstNode loop, Variable var +where escaping_capturing_looping_construct(capturing, loop, var) +select capturing, "Capture of loop variable '$@'", loop, var.getId() diff --git a/python/ql/src/Variables/LoopVariableCapture2.py b/python/ql/src/Variables/LoopVariableCapture2.py new file mode 100644 index 000000000000..e0b3db76b175 --- /dev/null +++ b/python/ql/src/Variables/LoopVariableCapture2.py @@ -0,0 +1,18 @@ + +#Make a list of functions to increment their arguments by 0 to 9. +def make_incrementers(): + result = [] + for i in range(10): + def incrementer(x, i=i): + return x + i + result.append(incrementer) + return result + +#This will pass +def test(): + incs = make_incrementers() + for x in range(10): + for y in range(10): + assert incs[x](y) == x+y + +test() \ No newline at end of file diff --git a/python/ql/src/Variables/MonkeyPatched.qll b/python/ql/src/Variables/MonkeyPatched.qll new file mode 100644 index 000000000000..5ee67edc0d14 --- /dev/null +++ b/python/ql/src/Variables/MonkeyPatched.qll @@ -0,0 +1,25 @@ +import python + + +predicate monkey_patched_builtin(string name) { + exists(AttrNode attr, SubscriptNode subscr, StrConst s | + subscr.isStore() and + subscr.getIndex().getNode() = s and + s.getText() = name and + subscr.getValue() = attr and + attr.getObject("__dict__").refersTo(theBuiltinModuleObject()) + ) + or + exists(CallNode call, ControlFlowNode bltn, StrConst s | + call.getArg(0) = bltn and + bltn.refersTo(theBuiltinModuleObject()) and + call.getArg(1).getNode() = s and + s.getText() = name and + call.getFunction().refersTo(builtin_object("setattr")) + ) + or + exists(AttrNode attr | + attr.isStore() and + attr.getObject(name).refersTo(theBuiltinModuleObject()) + ) +} diff --git a/python/ql/src/Variables/MultiplyDefined.py b/python/ql/src/Variables/MultiplyDefined.py new file mode 100644 index 000000000000..5d9ee98ca4c5 --- /dev/null +++ b/python/ql/src/Variables/MultiplyDefined.py @@ -0,0 +1,3 @@ +x = 42 +x = 12 +print x \ No newline at end of file diff --git a/python/ql/src/Variables/MultiplyDefined.qhelp b/python/ql/src/Variables/MultiplyDefined.qhelp new file mode 100644 index 000000000000..94200b44e145 --- /dev/null +++ b/python/ql/src/Variables/MultiplyDefined.qhelp @@ -0,0 +1,29 @@ + + + + + +

    Multiple assignments to a single variable without an intervening usage makes the first assignment redundant. +Its value is lost. +

    + +
    + +

    Ensure that the second assignment is in fact correct. +Then delete the first assignment (taking care not to delete right hand side if it has side effects).

    + +
    + +

    In this example, x is assigned the value of 42 but then the value is changed to 12 +before x is used. This makes the first assignment useless.

    + + +
    + + +
  • Python: Assignment statements.
  • + +
    +
    diff --git a/python/ql/src/Variables/MultiplyDefined.ql b/python/ql/src/Variables/MultiplyDefined.ql new file mode 100644 index 000000000000..14c95acb1fde --- /dev/null +++ b/python/ql/src/Variables/MultiplyDefined.ql @@ -0,0 +1,61 @@ +/** + * @name Variable defined multiple times + * @description Assignment to a variable occurs multiple times without any intermediate use of that variable + * @kind problem + * @tags maintainability + * useless-code + * external/cwe/cwe-563 + * @problem.severity warning + * @sub-severity low + * @precision very-high + * @id py/multiple-definition + */ + +import python +import Definition + +predicate multiply_defined(AstNode asgn1, AstNode asgn2, Variable v) { + /* Must be redefined on all possible paths in the CFG corresponding to the original source. + * For example, splitting may create a path where `def` is unconditionally redefined, even though + * it is not in the original source. */ + forex(Definition def, Definition redef | + def.getVariable() = v and + def = asgn1.getAFlowNode() and + redef = asgn2.getAFlowNode() | + def.isUnused() and + def.getARedef() = redef and + def.isRelevant() + ) +} + +predicate simple_literal(Expr e) { + e.(Num).getN() = "0" or + e instanceof NameConstant or + e instanceof List and not exists(e.(List).getAnElt()) or + e instanceof Tuple and not exists(e.(Tuple).getAnElt()) or + e instanceof Dict and not exists(e.(Dict).getAKey()) or + e.(StrConst).getText() = "" +} + +/** A multiple definition is 'uninteresting' if it sets a variable to a + * simple literal before reassigning it. + * x = None + * if cond: + * x = value1 + * else: + * x = value2 + */ +predicate uninteresting_definition(AstNode asgn1) { + exists(AssignStmt a | + a.getATarget() = asgn1 | + simple_literal(a.getValue()) + ) +} + + +from AstNode asgn1, AstNode asgn2, Variable v +where + multiply_defined(asgn1, asgn2, v) and + forall(Name el | el = asgn1.getParentNode().(Tuple).getAnElt() | multiply_defined(el, _, _)) and + not uninteresting_definition(asgn1) +select asgn1, "This assignment to '" + v.getId() + "' is unnecessary as it is redefined $@ before this value is used.", asgn2 as t, "here" diff --git a/python/ql/src/Variables/ShadowBuiltin.py b/python/ql/src/Variables/ShadowBuiltin.py new file mode 100644 index 000000000000..ab57e30d382a --- /dev/null +++ b/python/ql/src/Variables/ShadowBuiltin.py @@ -0,0 +1,8 @@ +def test(): + int = 1 # Variable should be renamed to avoid + def print_int(): # shadowing the int() built-in function + print int + print_int() + print int + +test() diff --git a/python/ql/src/Variables/ShadowBuiltin.qhelp b/python/ql/src/Variables/ShadowBuiltin.qhelp new file mode 100644 index 000000000000..ff6a02b69512 --- /dev/null +++ b/python/ql/src/Variables/ShadowBuiltin.qhelp @@ -0,0 +1,30 @@ + + + + +

    When a local variable is defined with the same name as a built-in type or function, the local +variable "shadows" or "hides" the built-in object. This can lead to +confusion as a reader of the code may expect the variable to refer to a built-in object. +

    + +
    + + +

    Change the name of the local variable so that it no longer matches the name of a built-in object. +

    + +
    + + + + + + + +
  • Python Standard Library: Built-in Functions, + Built-in Types.
  • + +
    +
    diff --git a/python/ql/src/Variables/ShadowBuiltin.ql b/python/ql/src/Variables/ShadowBuiltin.ql new file mode 100644 index 000000000000..8bf59411b915 --- /dev/null +++ b/python/ql/src/Variables/ShadowBuiltin.ql @@ -0,0 +1,64 @@ +/** + * @name Builtin shadowed by local variable + * @description Defining a local variable with the same name as a built-in object + * makes the built-in object unusable within the current scope and makes the code + * more difficult to read. + * @kind problem + * @tags maintainability + * readability + * @problem.severity recommendation + * @sub-severity low + * @precision medium + * @id py/local-shadows-builtin + */ + +import python +import Shadowing + +predicate white_list(string name) { + /* These are rarely used and thus unlikely to be confusing */ + name = "iter" or + name = "next" or + name = "input" or + name = "file" or + name = "apply" or + name = "slice" or + name = "buffer" or + name = "coerce" or + name = "intern" or + name = "exit" or + name = "quit" or + name = "license" or + /* These are short and/or hard to avoid */ + name = "dir" or + name = "id" or + name = "max" or + name = "min" or + name = "sum" or + name = "cmp" or + name = "chr" or + name = "ord" or + name = "bytes" or + name = "_" +} + +predicate shadows(Name d, string name, Scope scope, int line) { + exists(LocalVariable l | d.defines(l) and scope instanceof Function and + l.getId() = name and + exists(builtin_object(l.getId())) + ) and + d.getScope() = scope and + d.getLocation().getStartLine() = line and + not white_list(name) and + not optimizing_parameter(d) +} + +predicate first_shadowing_definition(Name d, string name) { + exists(int first, Scope scope | + shadows(d, name, scope, first) and + first = min(int line | shadows(_, name, scope, line))) +} + +from Name d, string name +where first_shadowing_definition(d, name) +select d, "Local variable " + name + " shadows a builtin variable." diff --git a/python/ql/src/Variables/ShadowGlobal.py b/python/ql/src/Variables/ShadowGlobal.py new file mode 100644 index 000000000000..672463f4bbb3 --- /dev/null +++ b/python/ql/src/Variables/ShadowGlobal.py @@ -0,0 +1,10 @@ +var = 2 # Global variable + +def test2(): + def print_var(): + var = 3 + print var # Local variable which "shadows" the global variable + print_var() # making it more difficult to determine which "var" + print var # is referenced + +test2() diff --git a/python/ql/src/Variables/ShadowGlobal.qhelp b/python/ql/src/Variables/ShadowGlobal.qhelp new file mode 100644 index 000000000000..52c883375706 --- /dev/null +++ b/python/ql/src/Variables/ShadowGlobal.qhelp @@ -0,0 +1,36 @@ + + + + +

    Python statements can access variables in both the local namespace and in the global namespace. +When a local and a global variable have the same name, the local variable "shadows" or "hides" the +global variable. When the variable is referenced, the variable with local scope is used unless you +explicitly use the global statement to reference the global variable. This can lead to +confusion as a reader of the code may expect the variable to refer to a global. +

    + +
    + + +

    Avoid using the same name for variables in local and global namespaces.

    + +
    + +

    The following simple example shows how a local variable can "shadow" a global variable. The local +variable should be renamed to make the code easier to interpret.

    + + + +
    + + +
  • J. Lusth, The Art and Craft of Programming - Python Edition, Section: Scope. University of Alabama, 2012. (Published online).
  • +
  • New Mexico Tech Computer Center: The global +statement: Declare access to a global name.
  • + + + +
    +
    diff --git a/python/ql/src/Variables/ShadowGlobal.ql b/python/ql/src/Variables/ShadowGlobal.ql new file mode 100644 index 000000000000..2bfb91e5a739 --- /dev/null +++ b/python/ql/src/Variables/ShadowGlobal.ql @@ -0,0 +1,66 @@ +/** + * @name Global shadowed by local variable + * @description Defining a local variable with the same name as a global variable + * makes the global variable unusable within the current scope and makes the code + * more difficult to read. + * @kind problem + * @tags maintainability + * readability + * @problem.severity recommendation + * @sub-severity low + * @precision medium + * @id py/local-shadows-global + */ + +import python +import Shadowing + +predicate shadows(Name d, GlobalVariable g, Scope scope, int line) { + exists(LocalVariable l | d.defines(l) and l.getId() = g.getId() and + scope instanceof Function and g.getScope() = scope.getScope() and + not exists(Import il, Import ig, Name gd | il.contains(d) and gd.defines(g) and ig.contains(gd)) and + not exists(Assign a | a.getATarget() = d and a.getValue() = g.getAnAccess()) + ) and + not exists(builtin_object(g.getId())) and + d.getScope() = scope and + d.getLocation().getStartLine() = line and + exists(Name defn | defn.defines(g) | + not exists(If i | i.isNameEqMain() | + i.contains(defn) + ) + ) and + not optimizing_parameter(d) +} + +/* pytest dynamically populates its namespace so, we cannot look directly for the pytest.fixture function */ +AttrNode pytest_fixture_attr() { + exists(ModuleObject pytest | + result.getObject("fixture").refersTo(pytest) + ) +} + +Object pytest_fixture() { + exists(CallNode call | + call.getFunction() = pytest_fixture_attr() + or + call.getFunction().(CallNode).getFunction() = pytest_fixture_attr() + | + call.refersTo(result) + ) +} + +/* pytest fixtures require that the parameter name is also a global */ +predicate assigned_pytest_fixture(GlobalVariable v) { + exists(NameNode def | def.defines(v) and def.(DefinitionNode).getValue().refersTo(pytest_fixture())) +} + +predicate first_shadowing_definition(Name d, GlobalVariable g) { + exists(int first, Scope scope | + shadows(d, g, scope, first) and + first = min(int line | shadows(_, g, scope, line))) +} + +from Name d, GlobalVariable g, Name def +where first_shadowing_definition(d, g) and not exists(Name n | n.deletes(g)) and + def.defines(g) and not assigned_pytest_fixture(g) and not g.getId() = "_" +select d, "Local variable '" + g.getId() + "' shadows a global variable defined $@.", def, "here" diff --git a/python/ql/src/Variables/Shadowing.qll b/python/ql/src/Variables/Shadowing.qll new file mode 100644 index 000000000000..5c56f5cacc27 --- /dev/null +++ b/python/ql/src/Variables/Shadowing.qll @@ -0,0 +1,13 @@ +import python + +/* Parameters with defaults that are used as an optimization. + * E.g. def f(x, len=len): ... + * (In general, this kind of optimization is not recommended.) + */ +predicate optimizing_parameter(Parameter p) { + exists(string name, Name glob | + p.getDefault() = glob | + glob.getId() = name and + p.asName().getId() = name + ) +} diff --git a/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.py b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.py new file mode 100644 index 000000000000..7b91ea8a6a4b --- /dev/null +++ b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.py @@ -0,0 +1,6 @@ + +# +def test(): + for t in [TypeA, TypeB]: + x = TypeA() + run_test(x) diff --git a/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.qhelp b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.qhelp new file mode 100644 index 000000000000..d537051c9f31 --- /dev/null +++ b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.qhelp @@ -0,0 +1,35 @@ + + + + +

    A for loop iteration variable is not used in the body of the loop, and the loop does not count the number of items in the sequence. +This is suspicious as there is rarely any reason to iterate over a sequence and not use the contents. +Not using the loop variable can often indicate a logical error or typo. +

    + +
    + +

    Carefully check that the loop variable should not be used. +If the variable is genuinely not being used and the code is correct, then rename the variable to _ +or unused to indicate to readers of the code that it is intentionally unused. +

    + +
    + +

    In this example, the for loop iteration variable x is never used. It appears that the +original test function was used to test TypeA and was subsequently modified to test TypeB as well. +

    + +

    +It is likely that the change from x = TypeA() to x = t() was forgotten. The fixed version is shown below. +

    + + +
    + +
  • Python Language Reference: The for statement.
  • +
  • Python Tutorial: For statements.
  • +
    +
    diff --git a/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql new file mode 100644 index 000000000000..4fbfd1a42a9e --- /dev/null +++ b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql @@ -0,0 +1,126 @@ +/** + * @name Suspicious unused loop iteration variable + * @description A loop iteration variable is unused, which suggests an error. + * @kind problem + * @tags maintainability + * correctness + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/unused-loop-variable + */ + +import python +import Definition + +predicate is_increment(Stmt s) { + /* x += n */ + s.(AugAssign).getValue() instanceof IntegerLiteral + or + /* x = x + n */ + exists(Name t, BinaryExpr add | + t = s.(AssignStmt).getTarget(0) and + add = s.(AssignStmt).getValue() and + add.getLeft().(Name).getVariable() = t.getVariable() and + add.getRight() instanceof IntegerLiteral + ) +} + +predicate counting_loop(For f) { + is_increment(f.getAStmt()) +} + +predicate empty_loop(For f) { + not exists(f.getStmt(1)) and f.getStmt(0) instanceof Pass +} + +predicate one_item_only(For f) { + not exists(Continue c | f.contains(c)) and + exists(Stmt s | + s = f.getBody().getLastItem() | + s instanceof Return + or + s instanceof Break + ) +} + +predicate points_to_call_to_range(ControlFlowNode f) { + /* (x)range is a function in Py2 and a class in Py3, so we must treat it as a plain object */ + exists(Object range, Object call | + range = builtin_object("range") or + range = builtin_object("xrange") + | + f.refersTo(call) and + call.(CallNode).getFunction().refersTo(range) + ) + or + /* In case points-to fails due to 'from six.moves import range' or similar. */ + exists(string range | + f.getNode().(Call).getFunc().(Name).getId() = range | + range = "range" or range = "xrange" + ) + or + /* If range is wrapped in a list it is still a range */ + exists(CallNode call | + f.refersTo(call) and + call = theListType().getACall() and + points_to_call_to_range(call.getArg(0)) + ) +} + +/** Whether n is a use of a variable that is a not effectively a constant. */ +predicate use_of_non_constant(Name n) { + exists(Variable var | + n.uses(var) and + /* use is local */ + not n.getScope() instanceof Module and + /* variable is not global */ + not var.getScope() instanceof Module | + /* Defined more than once (dynamically) */ + strictcount(Name def | def.defines(var)) > 1 or + exists(For f, Name def | f.contains(def) and def.defines(var)) or + exists(While w, Name def | w.contains(def) and def.defines(var)) + ) +} + +/** Whether loop body is implicitly repeating something N times. + * E.g. queue.add(None) + */ +predicate implicit_repeat(For f) { + not exists(f.getStmt(1)) and + exists(ImmutableLiteral imm | + f.getStmt(0).contains(imm) + ) and + not exists(Name n | f.getBody().contains(n) and use_of_non_constant(n)) +} + +/** Get the CFG node for the iterable relating to the for-statement `f` in a comprehension. + * The for-statement `f` is the artificial for-statement in a comprehension + * and the result is the iterable in that comprehension. + * E.g. gets `x` from `{ y for y in x }`. + */ +ControlFlowNode get_comp_iterable(For f) { + exists(Comp c | + c.getFunction().getStmt(0) = f | + c.getAFlowNode().getAPredecessor() = result + ) +} + +from For f, Variable v, string msg + +where f.getTarget() = v.getAnAccess() and + not f.getAStmt().contains(v.getAnAccess()) and + not points_to_call_to_range(f.getIter().getAFlowNode()) and + not points_to_call_to_range(get_comp_iterable(f)) and + not name_acceptable_for_unused_variable(v) and + not f.getScope().getName() = "genexpr" and + not empty_loop(f) and + not one_item_only(f) and + not counting_loop(f) and + not implicit_repeat(f) and + if exists(Name del | del.deletes(v) and f.getAStmt().contains(del)) then + msg = "' is deleted, but not used, in the loop body." + else + msg = "' is not used in the loop body." + +select f, "For loop variable '" + v.getId() + msg diff --git a/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariableFixed.py b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariableFixed.py new file mode 100644 index 000000000000..f28c276a626d --- /dev/null +++ b/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariableFixed.py @@ -0,0 +1,6 @@ + +# +def test(): + for t in [TypeA, TypeB]: + x = t + run_test(x) diff --git a/python/ql/src/Variables/Undefined.qll b/python/ql/src/Variables/Undefined.qll new file mode 100644 index 000000000000..eca28fe9aa5d --- /dev/null +++ b/python/ql/src/Variables/Undefined.qll @@ -0,0 +1,138 @@ +import python +import Loop +import semmle.python.security.TaintTracking + +/** Marker for "uninitialized". */ +class Uninitialized extends TaintKind { + + Uninitialized() { this = "undefined" } + +} + +/** A source of an uninitialized variable. + * Either the start of the scope or a deletion. + */ +class UninitializedSource extends TaintedDefinition { + + UninitializedSource() { + exists(FastLocalVariable var | + this.getSourceVariable() = var and + not var.escapes() | + this instanceof ScopeEntryDefinition + or + this instanceof DeletionDefinition + ) + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof Uninitialized + } + +} + +/** A loop where we are guaranteed (or is at least likely) to execute the body at least once. + */ +class AtLeastOnceLoop extends DataFlowExtension::DataFlowVariable { + + AtLeastOnceLoop() { + loop_entry_variables(this, _) + } + + /* If we are guaranteed to iterate over a loop at least once, then we can prune any edges that + * don't pass through the body. + */ + override predicate prunedSuccessor(EssaVariable succ) { + loop_entry_variables(this, succ) + } + +} + +private predicate loop_entry_variables(EssaVariable pred, EssaVariable succ) { + exists(PhiFunction phi, BasicBlock pb | + loop_entry_edge(pb, phi.getBasicBlock()) and + succ = phi.getVariable() and + pred = phi.getInput(pb) + ) +} + +private predicate loop_entry_edge(BasicBlock pred, BasicBlock loop) { + pred = loop.getAPredecessor() and + pred = loop.getImmediateDominator() and + exists(Stmt s | + loop_probably_executes_at_least_once(s) and + s.getAFlowNode().getBasicBlock() = loop + ) +} + +class UnitializedSanitizer extends Sanitizer { + + UnitializedSanitizer() { this = "use of variable" } + + override + predicate sanitizingDefinition(TaintKind taint, EssaDefinition def) { + // An assignment cannot leave a variable uninitialized + taint instanceof Uninitialized and + ( + def instanceof AssignmentDefinition + or + def instanceof ExceptionCapture + or + def instanceof ParameterDefinition + or + /* A use is a "sanitizer" of "uninitialized", as any use of an undefined + * variable will raise, making the subsequent code unreacahable. + */ + exists(def.(EssaNodeRefinement).getInput().getASourceUse()) + or + exists(def.(PhiFunction).getAnInput().getASourceUse()) + or + exists(def.(EssaEdgeRefinement).getInput().getASourceUse()) + ) + } + + override + predicate sanitizingNode(TaintKind taint, ControlFlowNode node) { + taint instanceof Uninitialized and + exists(EssaVariable v | + v.getASourceUse() = node and + not first_use(node, v) + ) + } + +} + +/** Since any use of a local will raise if it is uninitialized, then + * any use dominated by another use of the same variable must be defined, or is unreachable. + */ +private predicate first_use(NameNode u, EssaVariable v) { + v.getASourceUse() = u and + not exists(NameNode other | + v.getASourceUse() = other and + other.strictlyDominates(u) + ) +} + +/* Holds if `call` is a call of the form obj.method_name(...) and + * there is a function called `method_name` that can exit the program. + */ +private predicate maybe_call_to_exiting_function(CallNode call) { + exists(FunctionObject exits, string name | + exits.neverReturns() and exits.getName() = name + | + call.getFunction().(NameNode).getId() = name or + call.getFunction().(AttrNode).getName() = name + ) +} + +/** Prune edges where the predecessor block looks like it might contain a call to an exit function. */ +class ExitFunctionGuardedEdge extends DataFlowExtension::DataFlowVariable { + + predicate prunedSuccessor(EssaVariable succ) { + exists(CallNode exit_call | + succ.(PhiFunction).getInput(exit_call.getBasicBlock()) = this and + maybe_call_to_exiting_function(exit_call) + ) + } + +} + diff --git a/python/ql/src/Variables/UndefinedExport.py b/python/ql/src/Variables/UndefinedExport.py new file mode 100644 index 000000000000..1d1834854f73 --- /dev/null +++ b/python/ql/src/Variables/UndefinedExport.py @@ -0,0 +1,5 @@ +__all__ = ['spamm', 'troll', 'paywall'] + +def spam(): return 'Spam' +def troll(): return 'Troll' +def paywall(): return 'Pay wall' diff --git a/python/ql/src/Variables/UndefinedExport.qhelp b/python/ql/src/Variables/UndefinedExport.qhelp new file mode 100644 index 000000000000..f053de5048d4 --- /dev/null +++ b/python/ql/src/Variables/UndefinedExport.qhelp @@ -0,0 +1,37 @@ + + + + +

    When a module is imported using import *, all attributes listed in +__all__ are imported. If __all__ includes attributes that +are not defined in the module then an exception is triggered. This usually indicates +a typographic error in the attributes in __all__ or in the name of the +object.

    + +
    + + +

    Correct any typographic errors, either in the name of the object or in the string in +__all__. If there are no typographic errors, either delete the name from +__all__ or add the object to the module.

    + +
    + +

    +In the example, the function name spam has been misspelled in the __all__ list. +This will result in spamm being highlighted as an undefined export. +Correcting the spelling will fix the defect. +

    + + +
    + + +
  • Python Language Reference: The import statement.
  • +
  • Python Tutorial: Importing * from a Package.
  • + + +
    +
    diff --git a/python/ql/src/Variables/UndefinedExport.ql b/python/ql/src/Variables/UndefinedExport.ql new file mode 100644 index 000000000000..7ec2647a209f --- /dev/null +++ b/python/ql/src/Variables/UndefinedExport.ql @@ -0,0 +1,52 @@ +/** + * @name Explicit export is not defined + * @description Including an undefined attribute in __all__ causes an exception when + * the module is imported using '*' + * @kind problem + * @tags reliability + * maintainability + * @problem.severity error + * @sub-severity low + * @precision high + * @id py/undefined-export + */ + +import python + +/** Whether name is declared in the __all__ list of this module */ +predicate declaredInAll(Module m, StrConst name) +{ + exists(Assign a, GlobalVariable all | + a.defines(all) and a.getScope() = m and + all.getId() = "__all__" and ((List)a.getValue()).getAnElt() = name + ) +} + +predicate mutates_globals(PythonModuleObject m) { + exists(CallNode globals | + globals = theGlobalsFunction().(FunctionObject).getACall() and + globals.getScope() = m.getModule() | + exists(AttrNode attr | attr.getObject() = globals) + or + exists(SubscriptNode sub | sub.getValue() = globals and sub.isStore()) + ) + or + exists(Object enum_convert | + enum_convert.hasLongName("enum.Enum._convert") and + exists(CallNode call | + call.getScope() = m.getModule() + | + enum_convert.(FunctionObject).getACall() = call or + call.getFunction().refersTo(enum_convert) + ) + ) +} + +from PythonModuleObject m, StrConst name, string exported_name +where declaredInAll(m.getModule(), name) and +exported_name = name.strValue() and +not m.hasAttribute(exported_name) and +not (m.getShortName() = "__init__" and exists(m.getPackage().getModule().getSubModule(exported_name))) and +not exists(ImportStarNode imp | imp.getEnclosingModule() = m.getModule() | not imp.getModule().refersTo(_)) and +not mutates_globals(m) +select name, "The name '" + exported_name + "' is exported by __all__ but is not defined." \ No newline at end of file diff --git a/python/ql/src/Variables/UndefinedGlobal.py b/python/ql/src/Variables/UndefinedGlobal.py new file mode 100644 index 000000000000..cfbff8bfacb1 --- /dev/null +++ b/python/ql/src/Variables/UndefinedGlobal.py @@ -0,0 +1,12 @@ +import math + +angle = 0.01 + +sin(angle) # NameError: name 'sin' is not defined (function imported from 'math') + +math.sin(angle) # 'sin' function now correctly defined + +math.tan(angel) # NameError: name 'angel' not defined (typographic error) + +math.tan(angle) # Global variable now correctly defined + diff --git a/python/ql/src/Variables/UndefinedGlobal.qhelp b/python/ql/src/Variables/UndefinedGlobal.qhelp new file mode 100644 index 000000000000..f77a96aad192 --- /dev/null +++ b/python/ql/src/Variables/UndefinedGlobal.qhelp @@ -0,0 +1,36 @@ + + + + +

    This global variable may not be defined. +If this code is executed and the variable is undefined then a NameError will occur. +

    + +
    + + +

    Check that the name of the global variable is not a typographic error. If the name is correct +then define the variable or import the module that defines the function or method.

    + +

    If it is expected this variable will be initialized from another module before it is used, then the NameError may not occur. +Nonetheless, the code will be more robust and clearer if the variable is set to a default value in its own module. +

    + +
    + +

    The following examples show two different examples of undefined "global variables".

    + + + + +
    + + +
  • Python Standard Library: NameError.
  • +
  • The Python Tutorial: Modules.
  • + + +
    +
    diff --git a/python/ql/src/Variables/UndefinedGlobal.ql b/python/ql/src/Variables/UndefinedGlobal.ql new file mode 100644 index 000000000000..6e1b3d364290 --- /dev/null +++ b/python/ql/src/Variables/UndefinedGlobal.ql @@ -0,0 +1,132 @@ +/** + * @name Use of an undefined global variable + * @description Using a global variable before it is initialized causes an exception. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision low + * @id py/undefined-global-variable + */ + +import python +import Variables.MonkeyPatched +import Loop +import semmle.python.pointsto.PointsTo + +predicate guarded_against_name_error(Name u) { + exists(Try t | t.getBody().getAnItem().contains(u) | + ((Name)((ExceptStmt)t.getAHandler()).getType()).getId() = "NameError" + ) + or + exists(ConditionBlock guard, BasicBlock controlled, Call globals | + guard.getLastNode().getNode().contains(globals) or + guard.getLastNode().getNode() = globals | + globals.getFunc().(Name).getId() = "globals" and + guard.controls(controlled, _) and + controlled.contains(u.getAFlowNode()) + ) +} + +predicate contains_unknown_import_star(Module m) { + exists(ImportStar imp | imp.getScope() = m | + not exists(ModuleObject imported | imported.importedAs(imp.getImportedModuleName())) + or + exists(ModuleObject imported | + imported.importedAs(imp.getImportedModuleName()) | + not imported.exportsComplete() + ) + ) +} + +predicate undefined_use_in_function(Name u) { + exists(Function f | u.getScope().getScope*() = f and + /* Either function is a method or inner function or it is live at the end of the module scope */ + (not f.getScope() = u.getEnclosingModule() or ((ImportTimeScope)u.getEnclosingModule()).definesName(f.getName())) + and + /* There is a use, but not a definition of this global variable in the function or enclosing scope */ + exists(GlobalVariable v | u.uses(v) | + not exists(Assign a, Scope defnScope | + a.getATarget() = v.getAnAccess() and a.getScope() = defnScope | + defnScope = f or + /* Exclude modules as that case is handled more precisely below. */ + (defnScope = f.getScope().getScope*() and not defnScope instanceof Module) + ) + ) + ) + and + not ((ImportTimeScope)u.getEnclosingModule()).definesName(u.getId()) + and + not exists(ModuleObject m | m.getModule() = u.getEnclosingModule() | m.hasAttribute(u.getId())) + and + not globallyDefinedName(u.getId()) + and + not exists(SsaVariable var | var.getAUse().getNode() = u and not var.maybeUndefined()) + and + not guarded_against_name_error(u) + and + not (u.getEnclosingModule().isPackageInit() and u.getId() = "__path__") +} + +predicate undefined_use_in_class_or_module(Name u) { + exists(GlobalVariable v | u.uses(v)) + and + not exists(Function f | u.getScope().getScope*() = f) + and + exists(SsaVariable var | var.getAUse().getNode() = u | var.maybeUndefined()) + and + not guarded_against_name_error(u) + and + not exists(ModuleObject m | m.getModule() = u.getEnclosingModule() | m.hasAttribute(u.getId())) + and + not (u.getEnclosingModule().isPackageInit() and u.getId() = "__path__") + and + not globallyDefinedName(u.getId()) +} + +predicate use_of_exec(Module m) { + exists(Exec exec | exec.getScope() = m) + or + exists(CallNode call, FunctionObject exec | + exec.getACall() = call and call.getScope() = m | + exec = builtin_object("exec") or + exec = builtin_object("execfile") + ) +} + +predicate undefined_use(Name u) { + ( + undefined_use_in_class_or_module(u) + or + undefined_use_in_function(u) + ) and + not monkey_patched_builtin(u.getId()) and + not contains_unknown_import_star(u.getEnclosingModule()) and + not use_of_exec(u.getEnclosingModule()) and + not exists(u.getVariable().getAStore()) and + not u.refersTo(_) and + not probably_defined_in_loop(u) +} + +private predicate first_use_in_a_block(Name use) { + exists(GlobalVariable v, BasicBlock b, int i | + i = min(int j | b.getNode(j).getNode() = v.getALoad()) and b.getNode(i) = use.getAFlowNode() + ) +} + +predicate first_undefined_use(Name use) { + undefined_use(use) and + exists(GlobalVariable v | + v.getALoad() = use | + first_use_in_a_block(use) and + not exists(ControlFlowNode other | + other.getNode() = v.getALoad() and + other.getBasicBlock().strictlyDominates(use.getAFlowNode().getBasicBlock()) + ) + ) +} + +from Name u +where first_undefined_use(u) +select u, "This use of global variable '" + u.getId() + "' may be undefined." diff --git a/python/ql/src/Variables/UndefinedPlaceHolder.qhelp b/python/ql/src/Variables/UndefinedPlaceHolder.qhelp new file mode 100644 index 000000000000..726ac86a3864 --- /dev/null +++ b/python/ql/src/Variables/UndefinedPlaceHolder.qhelp @@ -0,0 +1,29 @@ + + + + +

    This place-holder variable may not be defined. +If this code is executed and the variable is undefined then a NameError will occur. +

    + +
    + + +

    Check that the name of the place-holder variable is not a typographic error. +If the name is correct, either define a value for the variable, or import the module that defines the function or method that sets the value. +

    + +

    If another module initializes this variable before it is used, then the NameError may not occur. +However, you can make the code more robust and clearer by setting the variable to a default value in its own module. +

    + +
    + + + +
  • Python Standard Library: NameError.
  • + +
    +
    diff --git a/python/ql/src/Variables/UndefinedPlaceHolder.ql b/python/ql/src/Variables/UndefinedPlaceHolder.ql new file mode 100644 index 000000000000..f3eb960045ca --- /dev/null +++ b/python/ql/src/Variables/UndefinedPlaceHolder.ql @@ -0,0 +1,54 @@ +/** + * @name Use of an undefined placeholder variable + * @description Using a variable before it is initialized causes an exception. + * @kind problem + * @problem.severity error + * @sub-severity low + * @precision medium + * @id py/undefined-placeholder-variable + */ + +import python +import Variables.MonkeyPatched + +/* Local variable part */ + +predicate initialized_as_local(PlaceHolder use) { + exists(SsaVariable l, Function f | f = use.getScope() and l.getAUse() = use.getAFlowNode() | + l.getVariable() instanceof LocalVariable and + not l.maybeUndefined() + ) +} + +/* Not a template member */ + +Class enclosing_class(PlaceHolder use) { + result.getAMethod() = use.getScope() +} + +predicate template_attribute(PlaceHolder use) { + exists(ImportTimeScope cls | + cls = enclosing_class(use) | + cls.definesName(use.getId()) + ) +} + +/* Global Stuff */ + +predicate not_a_global(PlaceHolder use) { + not exists(PythonModuleObject mo | mo.hasAttribute(use.getId()) and mo.getModule() = use.getEnclosingModule()) + and + not globallyDefinedName(use.getId()) and + not monkey_patched_builtin(use.getId()) and + not globallyDefinedName(use.getId()) +} + +from PlaceHolder p +where +not initialized_as_local(p) and +not template_attribute(p) and +not_a_global(p) +select p, "This use of place-holder variable '" + p.getId() + "' may be undefined" + + + diff --git a/python/ql/src/Variables/UninitializedLocal.py b/python/ql/src/Variables/UninitializedLocal.py new file mode 100644 index 000000000000..b079652e953c --- /dev/null +++ b/python/ql/src/Variables/UninitializedLocal.py @@ -0,0 +1,33 @@ +def test(): + var = 1 + def print_var(): + print var # Use variable from outer scope + print_var() + print var + + +def test1(): + var = 2 + def print_var(): + print var # Attempt to use variable from local scope. + var = 3 # Since this is not initialized yet, this results + print_var() # in an UnboundLocalError + print var + + +def test2(): + var = 2 + def print_var(): + var = 3 # Initialize local version of the variable + print var # Use variable from local scope. + print_var() # Note that this local variable "shadows" the variable from + print var # outer scope which makes code more difficult to interpret. + + +def test3(): + var = 4 + def print_var(): + nonlocal var # Use non-local variable from outer scope. + print var + print_var() + print var \ No newline at end of file diff --git a/python/ql/src/Variables/UninitializedLocal.qhelp b/python/ql/src/Variables/UninitializedLocal.qhelp new file mode 100644 index 000000000000..e9af644d6823 --- /dev/null +++ b/python/ql/src/Variables/UninitializedLocal.qhelp @@ -0,0 +1,41 @@ + + + + + +

    This local variable may be used before it is defined. If a variable is assigned to in a function +and not explicitly declared global or nonlocal then it is assumed to be a +local variable. +If it is used before it is defined then an UnboundLocalError will be raised. +

    + +
    + + +

    Review the code and consider the intended scope of the variable. Determine whether the variable +should be global or local in scope. If a global variable is required then add a global +statement, or in Python 3 you can use a nonlocal statement if the variable occurs in an +enclosing function. Otherwise, ensure that the variable is defined before it is used.

    + +
    + +

    The following code includes different functions that use variables. test1() +fails with an UnboundLocalError because the local variable var is used +before it is initialized.

    + + + + +
    + + +
  • Python Standard Library: Built-in Exceptions: UnboundLocalError.
  • +
  • Python Frequently Asked Questions: Why am I getting an UnboundLocalError when the variable has a value?.
  • +
  • Python Course: Global and Local Variables.
  • +
  • Python Language Reference: The global statement, + The nonlocal statement.
  • + +
    +
    diff --git a/python/ql/src/Variables/UninitializedLocal.ql b/python/ql/src/Variables/UninitializedLocal.ql new file mode 100644 index 000000000000..2e01e2f7c3d5 --- /dev/null +++ b/python/ql/src/Variables/UninitializedLocal.ql @@ -0,0 +1,42 @@ +/** + * @name Potentially uninitialized local variable + * @description Using a local variable before it is initialized causes an UnboundLocalError. + * @kind problem + * @tags reliability + * correctness + * @problem.severity error + * @sub-severity low + * @precision medium + * @id py/uninitialized-local-variable + */ + +import python +import Undefined + + +predicate uninitialized_local(NameNode use) { + exists(FastLocalVariable local | + use.uses(local) or use.deletes(local) | + not local.escapes() + ) + and + ( + any(Uninitialized uninit).taints(use) + or + not exists(EssaVariable var | var.getASourceUse() = use) + ) +} + +predicate explicitly_guarded(NameNode u) { + exists(Try t | + t.getBody().contains(u.getNode()) and + t.getAHandler().getType().refersTo(theNameErrorType()) + ) +} + + +from NameNode u +where uninitialized_local(u) and not explicitly_guarded(u) +select u.getNode(), "Local variable '" + u.getId() + "' may be used before it is initialized." + + diff --git a/python/ql/src/Variables/UnusedLocalVariable.py b/python/ql/src/Variables/UnusedLocalVariable.py new file mode 100644 index 000000000000..d0d46f0e1df1 --- /dev/null +++ b/python/ql/src/Variables/UnusedLocalVariable.py @@ -0,0 +1,11 @@ +import random + +def write_random_to_file(): + no = random.randint(1, 10) + with open("random.txt", "w") as file: + file.write(str(no)) + return no + +def write_random(): + random_no = write_random_to_file() + print "A random number was written to random.txt" \ No newline at end of file diff --git a/python/ql/src/Variables/UnusedLocalVariable.qhelp b/python/ql/src/Variables/UnusedLocalVariable.qhelp new file mode 100644 index 000000000000..e24b5d174644 --- /dev/null +++ b/python/ql/src/Variables/UnusedLocalVariable.qhelp @@ -0,0 +1,31 @@ + + + + +

    A local variable is defined (by an assignment) but never used. +

    + + + + +
    + +

    If the variable is included for documentation purposes or is otherwise intentionally unused, then change its name to indicate that it is unused, +otherwise delete the assignment (taking care not to delete right hand side if it has side effects).

    + +
    + +

    In this example, the random_no variable is never read but its assignment +has a side effect. Because of this it is important to remove only the left hand side of the +assignment in line 10.

    + + +
    + + +
  • Python: Assignment statements.
  • + +
    +
    diff --git a/python/ql/src/Variables/UnusedLocalVariable.ql b/python/ql/src/Variables/UnusedLocalVariable.ql new file mode 100644 index 000000000000..42f28c5e0852 --- /dev/null +++ b/python/ql/src/Variables/UnusedLocalVariable.ql @@ -0,0 +1,34 @@ +/** + * @name Unused local variable + * @description Local variable is defined but not used + * @kind problem + * @tags maintainability + * useless-code + * external/cwe/cwe-563 + * @problem.severity recommendation + * @sub-severity high + * @precision very-high + * @id py/unused-local-variable + */ + +import python +import Definition + +predicate unused_local(Name unused, LocalVariable v) { + forex(Definition def | + def.getNode() = unused | + def.getVariable() = v and + def.isUnused() and + not exists(def.getARedef()) and + def.isRelevant() and + not exists(def.getNode().getParentNode().(FunctionDef).getDefinedFunction().getADecorator()) and + not exists(def.getNode().getParentNode().(ClassDef).getDefinedClass().getADecorator()) + ) +} + + +from Name unused, LocalVariable v +where unused_local(unused, v) and +// If unused is part of a tuple, count it as unused if all elements of that tuple are unused. +forall(Name el | el = unused.getParentNode().(Tuple).getAnElt() | unused_local(el, _)) +select unused, "The value assigned to local variable '" + v.getId() + "' is never used." diff --git a/python/ql/src/Variables/UnusedModuleVariable.py b/python/ql/src/Variables/UnusedModuleVariable.py new file mode 100644 index 000000000000..91bc09518482 --- /dev/null +++ b/python/ql/src/Variables/UnusedModuleVariable.py @@ -0,0 +1,9 @@ +import random + +def write_random_to_file(): + no = random.randint(1, 10) + with open("random.txt", "w") as file: + file.write(str(no)) + return no + +random_no = write_random_to_file() \ No newline at end of file diff --git a/python/ql/src/Variables/UnusedModuleVariable.qhelp b/python/ql/src/Variables/UnusedModuleVariable.qhelp new file mode 100644 index 000000000000..b5e57bf55ce3 --- /dev/null +++ b/python/ql/src/Variables/UnusedModuleVariable.qhelp @@ -0,0 +1,34 @@ + + + + +

    A global (module-level) variable is defined (by an assignment) but never used +and is not explicitly made public by inclusion in the __all__ list. +

    + + + + +
    + +

    If the variable is included for documentation purposes or is otherwise intentionally unused, then change its name to indicate that it is unused, +otherwise delete the assignment (taking care not to delete right hand side if it has side effects).

    + +
    + +

    In this example, the random_no variable is never read but its assignment +has a side effect. Because of this it is important to only remove the left hand side of the +assignment in line 9.

    + + +
    + + +
  • Python: Assignment statements, + The import statement.
  • +
  • Python Tutorial: Importing * from a package.
  • + +
    +
    diff --git a/python/ql/src/Variables/UnusedModuleVariable.ql b/python/ql/src/Variables/UnusedModuleVariable.ql new file mode 100644 index 000000000000..888b9546ce1a --- /dev/null +++ b/python/ql/src/Variables/UnusedModuleVariable.ql @@ -0,0 +1,62 @@ +/** + * @name Unused global variable + * @description Global variable is defined but not used + * @kind problem + * @tags efficiency + * useless-code + * external/cwe/cwe-563 + * @problem.severity recommendation + * @sub-severity low + * @precision high + * @id py/unused-global-variable + */ + +import python +import Definition + +/** Whether the module contains an __all__ definition, + * but it is more complex than a simple list of strings */ +predicate complex_all(Module m) { + exists(Assign a, GlobalVariable all | + a.defines(all) and a.getScope() = m and all.getId() = "__all__" | + not a.getValue() instanceof List or + exists(Expr e | + e = a.getValue().(List).getAnElt() | + not e instanceof StrConst + ) + ) + or + exists(Call c, GlobalVariable all | + c.getFunc().(Attribute).getObject() = all.getALoad() and + c.getScope() = m and all.getId() = "__all__" + ) +} + +predicate unused_global(Name unused, GlobalVariable v) { + not exists(ImportingStmt is | is.contains(unused)) and + forex(DefinitionNode defn | + defn.getNode() = unused | + not defn.getValue().getNode() instanceof FunctionExpr and + not defn.getValue().getNode() instanceof ClassExpr and + not exists(Name u | + // A use of the variable + u.uses(v) | + // That is reachable from this definition, directly + defn.strictlyReaches(u.getAFlowNode()) + or // indirectly + defn.getBasicBlock().reachesExit() and u.getScope() != unused.getScope() + ) and + not unused.getEnclosingModule().getAnExport() = v.getId() and + not exists(unused.getParentNode().(ClassDef).getDefinedClass().getADecorator()) and + not exists(unused.getParentNode().(FunctionDef).getDefinedFunction().getADecorator()) and + unused.defines(v) and + not name_acceptable_for_unused_variable(v) and + not complex_all(unused.getEnclosingModule()) + ) +} + +from Name unused, GlobalVariable v +where unused_global(unused, v) and +// If unused is part of a tuple, count it as unused if all elements of that tuple are unused. +forall(Name el | el = unused.getParentNode().(Tuple).getAnElt() | unused_global(el, _)) +select unused, "The global variable '" + v.getId() + "' is not used." diff --git a/python/ql/src/Variables/UnusedParameter.py b/python/ql/src/Variables/UnusedParameter.py new file mode 100644 index 000000000000..b0eee5e4f917 --- /dev/null +++ b/python/ql/src/Variables/UnusedParameter.py @@ -0,0 +1,5 @@ +import random + +def write_to_file(text, filename): + with open("log.txt", "w") as file: + file.write(text) diff --git a/python/ql/src/Variables/UnusedParameter.qhelp b/python/ql/src/Variables/UnusedParameter.qhelp new file mode 100644 index 000000000000..4fa34ba65ec7 --- /dev/null +++ b/python/ql/src/Variables/UnusedParameter.qhelp @@ -0,0 +1,30 @@ + + + + + + +

    A parameter is never used. +

    + + + +
    + + +

    Delete the parameter from the relevant function or method. +If that is not possible (due to overriding or similar) rename the parameter + as described above. + +

    + + +

    In this example the parameter filename is ignored which is misleading. + +

    + + +
    +
    diff --git a/python/ql/src/Variables/UnusedParameter.ql b/python/ql/src/Variables/UnusedParameter.ql new file mode 100644 index 000000000000..e27d151b72ea --- /dev/null +++ b/python/ql/src/Variables/UnusedParameter.ql @@ -0,0 +1,31 @@ +/** + * @name Unused parameter + * @description Parameter is defined but not used + * @kind problem + * @tags maintainability + * @problem.severity recommendation + * @sub-severity high + * @precision medium + * @id py/unused-parameter + */ + +import python +import Definition + + +predicate unused_parameter(FunctionObject f, LocalVariable v) { + v.isParameter() and + v.getScope() = f.getFunction() and + not name_acceptable_for_unused_variable(v) and + not exists(NameNode u | u.uses(v)) and + not exists(Name inner, LocalVariable iv | inner.uses(iv) and iv.getId() = v.getId() and inner.getScope().getScope() = v.getScope()) +} + +predicate is_abstract(FunctionObject func) { + ((Name)func.getFunction().getADecorator()).getId().matches("%abstract%") +} + +from PyFunctionObject f, LocalVariable v +where v.getId() != "self" and unused_parameter(f, v) and not f.isOverridingMethod() and not f.isOverriddenMethod() and +not is_abstract(f) +select f, "The parameter '" + v.getId() + "' is never used." diff --git a/python/ql/src/Variables/UnusedTuple.qhelp b/python/ql/src/Variables/UnusedTuple.qhelp new file mode 100644 index 000000000000..e1fd8c54a92c --- /dev/null +++ b/python/ql/src/Variables/UnusedTuple.qhelp @@ -0,0 +1,12 @@ + + + + +

    Variables that are defined in a group, for example x, y = func() are handled collectively. +If they are all unused, then this is reported. Otherwise they are all treated as used. +

    + +
    +
    diff --git a/python/ql/src/Variables/UnusedVariableNaming.qhelp b/python/ql/src/Variables/UnusedVariableNaming.qhelp new file mode 100644 index 000000000000..f02a3fa2e386 --- /dev/null +++ b/python/ql/src/Variables/UnusedVariableNaming.qhelp @@ -0,0 +1,24 @@ + + + + +

    It is sometimes necessary to have a variable which is not used. +These unused variables should have distinctive names, to make it clear to readers of the code that they are deliberately not used. +The most common conventions for indicating this are to name the variable _ or to start the name of the +variable with unused or _unused. +

    + +

    +The query accepts the following names for variables that are intended to be unused: +

    +
      +
    • Any name consisting entirely of underscores.
    • +
    • Any name containing unused.
    • +
    • The names dummy or empty.
    • +
    • Any "special" name of the form __xxx__.
    • +
    + +
    +
    \ No newline at end of file diff --git a/python/ql/src/analysis/AlertSuppression.ql b/python/ql/src/analysis/AlertSuppression.ql new file mode 100644 index 000000000000..56622f005ca9 --- /dev/null +++ b/python/ql/src/analysis/AlertSuppression.ql @@ -0,0 +1,126 @@ +/** + * @name Alert suppression + * @description Generates information about alert suppressions. + * @kind alert-suppression + * @id py/alert-suppression + */ + +import python + +/** + * An alert suppression comment. + */ +abstract class SuppressionComment extends Comment { + + /** Gets the scope of this suppression. */ + abstract SuppressionScope getScope(); + + /** Gets the suppression annotation in this comment. */ + abstract string getAnnotation(); + + /** + * Holds if this comment applies to the range from column `startcolumn` of line `startline` + * to column `endcolumn` of line `endline` in file `filepath`. + */ + abstract predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn); + +} + +/** + * An alert comment that applies to a single line + */ +abstract class LineSuppressionComment extends SuppressionComment { + + LineSuppressionComment() { + exists(string filepath, int l | + this.getLocation().hasLocationInfo(filepath, l, _, _, _) and + any(AstNode a).getLocation().hasLocationInfo(filepath, l, _, _, _) + ) + } + + /** Gets the scope of this suppression. */ + override SuppressionScope getScope() { + result = this + } + + override predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + this.getLocation().hasLocationInfo(filepath, startline, _, endline, endcolumn) and + startcolumn = 1 + } + +} + +/** + * An lgtm suppression comment. + */ +class LgtmSuppressionComment extends LineSuppressionComment { + + string annotation; + + LgtmSuppressionComment() { + exists(string all | + all = this.getContents() + | + // match `lgtm[...]` anywhere in the comment + annotation = all.regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _) + or + // match `lgtm` at the start of the comment and after semicolon + annotation = all.regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim() + ) + } + + /** Gets the suppression annotation in this comment. */ + override string getAnnotation() { + result = annotation + } + +} + +/** + * A noqa suppression comment. Both pylint and pyflakes respect this, so lgtm ought to too. + */ +class NoqaSuppressionComment extends LineSuppressionComment { + + NoqaSuppressionComment() { + this.getContents().toLowerCase().regexpMatch("\\s*noqa\\s*") + } + + override string getAnnotation() { + result = "lgtm" + } + +} + + +/** + * The scope of an alert suppression comment. + */ +class SuppressionScope extends @py_comment { + + SuppressionScope() { + this instanceof SuppressionComment + } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [LGTM locations](https://lgtm.com/help/ql/locations). + */ + predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + this.(SuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets a textual representation of this element. */ + string toString() { + result = "suppression range" + } + +} + +from SuppressionComment c +select c, // suppression comment + c.getContents(), // text of suppression comment (excluding delimiters) + c.getAnnotation(), // text of suppression annotation + c.getScope() // scope of suppression diff --git a/python/ql/src/analysis/CallGraphEfficiency.ql b/python/ql/src/analysis/CallGraphEfficiency.ql new file mode 100644 index 000000000000..f15565687334 --- /dev/null +++ b/python/ql/src/analysis/CallGraphEfficiency.ql @@ -0,0 +1,25 @@ +/** Compute the total call-graph facts, the total size of the call-graph relation and + * the ratio of the two in relation to the depth of context. + */ + + +import python +import semmle.python.pointsto.PointsTo +import semmle.python.pointsto.PointsToContext + +from int total_facts, int total_size, int depth, float efficiency +where +total_facts = strictcount(ControlFlowNode call, FunctionObject func | + exists(PointsToContext ctx | + call = PointsTo::get_a_call(func, ctx) and + depth = ctx.getDepth() + ) +) +and +total_size = strictcount(ControlFlowNode call, FunctionObject func, PointsToContext ctx | + call = PointsTo::get_a_call(func, ctx) and + depth = ctx.getDepth() +) +and +efficiency = 100.0 * total_facts / total_size +select depth, total_facts, total_size, efficiency diff --git a/python/ql/src/analysis/CallGraphMarginalEfficiency.ql b/python/ql/src/analysis/CallGraphMarginalEfficiency.ql new file mode 100644 index 000000000000..72ca0383d5c8 --- /dev/null +++ b/python/ql/src/analysis/CallGraphMarginalEfficiency.ql @@ -0,0 +1,29 @@ +/** Compute the marginal increase call-graph facts, the total size of the call-graph relation and + * the ratio of the two in relation to the depth of context. + */ + +import python +import semmle.python.pointsto.PointsTo +import semmle.python.pointsto.PointsToContext + +from int total_facts, int total_size, int depth, float efficiency +where +total_facts = strictcount(ControlFlowNode call, FunctionObject func | + exists(PointsToContext ctx | + call = PointsTo::get_a_call(func, ctx) and + depth = ctx.getDepth() + and not + exists(PointsToContext shallower | + call = PointsTo::get_a_call(func, shallower) and + shallower.getDepth() < depth + ) + ) +) +and +total_size = strictcount(ControlFlowNode call, FunctionObject func, PointsToContext ctx | + call = PointsTo::get_a_call(func, ctx) and + depth = ctx.getDepth() +) +and +efficiency = 100.0 * total_facts / total_size +select depth, total_facts, total_size, efficiency diff --git a/python/ql/src/analysis/ContextEfficiency.ql b/python/ql/src/analysis/ContextEfficiency.ql new file mode 100644 index 000000000000..e25d69715b90 --- /dev/null +++ b/python/ql/src/analysis/ContextEfficiency.ql @@ -0,0 +1,25 @@ +/** Compute the total points-to facts, the total size of the points-to relation and + * the ratio of the two in relation to the depth of context. + */ + + +import python +import semmle.python.pointsto.PointsTo +import semmle.python.pointsto.PointsToContext + +from int total_facts, int total_size, int depth, float efficiency +where +total_facts = strictcount(ControlFlowNode f, Object value, ClassObject cls | + exists(PointsToContext ctx | + PointsTo::points_to(f, ctx, value, cls, _) and + depth = ctx.getDepth() + ) +) +and +total_size = strictcount(ControlFlowNode f, Object value, ClassObject cls, PointsToContext ctx, ControlFlowNode orig | + PointsTo::points_to(f, ctx, value, cls, orig) and + depth = ctx.getDepth() +) +and +efficiency = 100.0 * total_facts / total_size +select depth, total_facts, total_size, efficiency diff --git a/python/ql/src/analysis/ContextMarginalEfficiency.ql b/python/ql/src/analysis/ContextMarginalEfficiency.ql new file mode 100644 index 000000000000..f48e05301233 --- /dev/null +++ b/python/ql/src/analysis/ContextMarginalEfficiency.ql @@ -0,0 +1,32 @@ +/** Compute the marginal increase points-to facts, the total size of the points-to relation and + * the ratio of the two in relation to the depth of context. + */ + +import python +import semmle.python.pointsto.PointsTo +import semmle.python.pointsto.PointsToContext + +int depth(ControlFlowNode f, Object value, ClassObject cls) { + exists(PointsToContext ctx | + PointsTo::points_to(f, ctx, value, cls, _) and + result = ctx.getDepth() + ) +} + +int shallowest(ControlFlowNode f, Object value, ClassObject cls) { + result = min(int x | x = depth(f, value, cls)) +} + +from int total_facts, int total_size, int depth, float efficiency +where +total_facts = strictcount(ControlFlowNode f, Object value, ClassObject cls | + depth = shallowest(f, value, cls) +) +and +total_size = strictcount(ControlFlowNode f, Object value, ClassObject cls, PointsToContext ctx, ControlFlowNode orig | + PointsTo::points_to(f, ctx, value, cls, orig) and + depth = ctx.getDepth() +) +and +efficiency = 100.0 * total_facts / total_size +select depth, total_facts, total_size, efficiency \ No newline at end of file diff --git a/python/ql/src/analysis/CrossProjectDefinitions.qll b/python/ql/src/analysis/CrossProjectDefinitions.qll new file mode 100644 index 000000000000..e09fce9e9a2e --- /dev/null +++ b/python/ql/src/analysis/CrossProjectDefinitions.qll @@ -0,0 +1,115 @@ +/** + * Symbols for crosss-project jump-to-definition resolution. + */ + import python + +import semmle.dataflow.SSA +import semmle.python.pointsto.PointsTo + +private newtype TSymbol = + TModule(Module m) + or + TMember(Symbol outer, string part) { + exists(Object o | + outer.resolvesTo() = o | + o.(ModuleObject).hasAttribute(part) + or + o.(ClassObject).hasAttribute(part) + ) + } + +/** A "symbol" referencing an object in another module + * Symbols are represented by the module name and the dotted name by which the + * object would be referred to in that module. + * For example for the code: + * ``` + * class C: + * def m(self): pass + * ``` + * If the code were in a module `mod`, + * then symbol for the method `m` would be "mod/C.m" + */ +class Symbol extends TSymbol { + + string toString() { + exists(Module m | + this = TModule(m) and result = m.getName() + ) + or + exists(TModule outer, string part | + this = TMember(outer, part) and + outer = TModule(_) and + result = outer.(Symbol).toString() + "/" + part + ) + or + exists(TMember outer, string part | + this = TMember(outer, part) and + outer = TMember(_, _) and + result = outer.(Symbol).toString() + "." + part + ) + } + + /** Finds the `AstNode` that this `Symbol` refers to. + */ + AstNode find() { + this = TModule(result) + or + exists(Symbol s, string name | + this = TMember(s, name) | + exists(ClassObject cls | + s.resolvesTo() = cls and + cls.attributeRefersTo(name, _, result.getAFlowNode()) + ) + or + exists(ModuleObject m | + s.resolvesTo() = m and + m.attributeRefersTo(name, _, result.getAFlowNode()) + ) + ) + } + + /** Find the class or module `Object` that this `Symbol` refers to, if + * this `Symbol` refers to a class or module. + */ + Object resolvesTo() { + this = TModule(result.(ModuleObject).getModule()) + or + exists(Symbol s, string name, Object o | + this = TMember(s, name) and + o = s.resolvesTo() and + result = attribute_in_scope(o, name) + ) + } + + /** Gets the `Module` for the module part of this `Symbol`. + * For example, this would return the `os` module for the `Symbol` "os/environ". + */ + Module getModule() { + this = TModule(result) + or + exists(Symbol outer | + this = TMember(outer, _) and result = outer.getModule() + ) + } + + /** Gets the `Symbol` that is the named member of this `Symbol`. + */ + Symbol getMember(string name) { + result = TMember(this, name) + } + +} + +/* Helper for `Symbol`.resolvesTo() */ +private Object attribute_in_scope(Object obj, string name) { + exists(ClassObject cls | + cls = obj | + cls.lookupAttribute(name) = result and result.(ControlFlowNode).getScope() = cls.getPyClass() + ) + or + exists(ModuleObject mod | + mod = obj | + mod.getAttribute(name) = result and result.(ControlFlowNode).getScope() = mod.getModule() + and not result.(ControlFlowNode).isEntryNode() + ) +} diff --git a/python/ql/src/analysis/DefinitionTracking.qll b/python/ql/src/analysis/DefinitionTracking.qll new file mode 100644 index 000000000000..f3e23270370d --- /dev/null +++ b/python/ql/src/analysis/DefinitionTracking.qll @@ -0,0 +1,483 @@ +/** + * Definition tracking for jump-to-defn query. + */ + import python + +import semmle.dataflow.SSA +import semmle.python.pointsto.PointsTo + +private newtype TDefinition = + TLocalDefinition(AstNode a) { + a instanceof Expr or a instanceof Stmt or a instanceof Module + } + +/** A definition for the purposes of jump-to-definition. + */ +class Definition extends TLocalDefinition { + + + string toString() { + result = "Definition " + this.getAstNode().getLocation().toString() + } + + AstNode getAstNode() { + this = TLocalDefinition(result) + } + + Module getModule() { + result = this.getAstNode().getScope().getEnclosingModule() + } + + Location getLocation() { + result = this.getAstNode().getLocation() + } + +} + +private predicate jump_to_defn(ControlFlowNode use, Definition defn) { + exists(EssaVariable var | + use = var.getASourceUse() and + ssa_variable_defn(var, defn) + ) + or + exists(string name | + use.isLoad() and + jump_to_defn_attribute(use.(AttrNode).getObject(name), name, defn) + ) + or + exists(PythonModuleObject mod | + use.(ImportExprNode).refersTo(mod) and + defn.getAstNode() = mod.getModule() + ) + or + exists(PythonModuleObject mod, string name | + use.(ImportMemberNode).getModule(name).refersTo(mod) and + scope_jump_to_defn_attribute(mod.getModule(), name, defn) + ) + or + exists(PackageObject package | + use.(ImportExprNode).refersTo(package) and + defn.getAstNode() = package.getInitModule().getModule() + ) + or + exists(PackageObject package, string name | + use.(ImportMemberNode).getModule(name).refersTo(package) and + scope_jump_to_defn_attribute(package.getInitModule().getModule(), name, defn) + ) + or + (use instanceof PyFunctionObject or use instanceof ClassObject) and + defn.getAstNode() = use.getNode() +} + +/* Prefer class and functions to class-expressions and function-expressions. */ +private predicate preferred_jump_to_defn(Expr use, Definition def) { + not use instanceof ClassExpr and + not use instanceof FunctionExpr and + jump_to_defn(use.getAFlowNode(), def) +} + +private predicate unique_jump_to_defn(Expr use, Definition def) { + preferred_jump_to_defn(use, def) and + not exists(Definition other | + other != def and + preferred_jump_to_defn(use, other) + ) +} + +private predicate ssa_variable_defn(EssaVariable var, Definition defn) { + ssa_defn_defn(var.getDefinition(), defn) +} + +/** Holds if the phi-function `phi` refers to (`value`, `cls`, `origin`) given the context `context`. */ +private predicate ssa_phi_defn(PhiFunction phi, Definition defn) { + ssa_variable_defn(phi.getAnInput(), defn) +} + +/** Holds if the ESSA defn `def` refers to (`value`, `cls`, `origin`) given the context `context`. */ +private predicate ssa_defn_defn(EssaDefinition def, Definition defn) { + ssa_phi_defn(def, defn) + or + ssa_node_defn(def, defn) + or + ssa_filter_defn(def, defn) + or + ssa_node_refinement_defn(def, defn) +} + +/** Holds if ESSA edge refinement, `def`, is defined by `defn` */ +predicate ssa_filter_defn(PyEdgeRefinement def, Definition defn) { + ssa_variable_defn(def.getInput(), defn) +} + +/** Holds if ESSA defn, `uniphi`,is defined by `defn` */ +predicate uni_edged_phi_defn(SingleSuccessorGuard uniphi, Definition defn) { + ssa_variable_defn(uniphi.getInput(), defn) +} + +pragma [noinline] +private predicate ssa_node_defn(EssaNodeDefinition def, Definition defn) { + assignment_jump_to_defn(def, defn) + or + parameter_defn(def, defn) + or + delete_defn(def, defn) + or + scope_entry_defn(def, defn) + or + implicit_submodule_defn(def, defn) +} + +/* Definition for normal assignments `def = ...` */ +private predicate assignment_jump_to_defn(AssignmentDefinition def, Definition defn) { + defn = TLocalDefinition(def.getValue().getNode()) +} + +pragma [noinline] +private predicate ssa_node_refinement_defn(EssaNodeRefinement def, Definition defn) { + method_callsite_defn(def, defn) + or + import_star_defn(def, defn) + or + attribute_assignment_defn(def, defn) + or + callsite_defn(def, defn) + or + argument_defn(def, defn) + or + attribute_delete_defn(def, defn) + or + uni_edged_phi_defn(def, defn) +} + + +/* Definition for parameter. `def foo(param): ...` */ +private predicate parameter_defn(ParameterDefinition def, Definition defn) { + defn.getAstNode() = def.getDefiningNode().getNode() +} + +/* Definition for deletion: `del name` */ +private predicate delete_defn(DeletionDefinition def, Definition defn) { + none() +} + +/* Implicit "defn" of the names of submodules at the start of an `__init__.py` file. + */ +private predicate implicit_submodule_defn(ImplicitSubModuleDefinition def, Definition defn) { + exists(PackageObject package, ModuleObject mod | + package.getInitModule().getModule() = def.getDefiningNode().getScope() and + mod = package.submodule(def.getSourceVariable().getName()) and + defn.getAstNode() = mod.getModule() + ) + +} + +/* Helper for scope_entry_value_transfer(...). Transfer of values from the callsite to the callee, for enclosing variables, but not arguments/parameters */ +private predicate scope_entry_value_transfer_at_callsite(EssaVariable pred_var, ScopeEntryDefinition succ_def) { + exists(CallNode callsite, FunctionObject f | + f.getACall() = callsite and + pred_var.getSourceVariable() = succ_def.getSourceVariable() and + pred_var.getAUse() = callsite and + succ_def.getDefiningNode() = f.getFunction().getEntryNode() + ) +} + +/* Model the transfer of values at scope-entry points. Transfer from `pred_var, pred_context` to `succ_def, succ_context` */ +private +predicate scope_entry_value_transfer(EssaVariable pred_var, ScopeEntryDefinition succ_def) { + BaseFlow::scope_entry_value_transfer_from_earlier(pred_var, _, succ_def, _) + or + scope_entry_value_transfer_at_callsite(pred_var, succ_def) + or + class_entry_value_transfer(pred_var, succ_def) +} + +/* Helper for scope_entry_value_transfer */ +private +predicate class_entry_value_transfer(EssaVariable pred_var, ScopeEntryDefinition succ_def) { + exists(ImportTimeScope scope, ControlFlowNode class_def | + class_def = pred_var.getAUse() and + scope.entryEdge(class_def, succ_def.getDefiningNode()) and + pred_var.getSourceVariable() = succ_def.getSourceVariable() + ) +} + +/* Definition for implicit variable declarations at scope-entry. */ +pragma [noinline] +private predicate scope_entry_defn(ScopeEntryDefinition def, Definition defn) { + /* Transfer from another scope */ + exists(EssaVariable var | + scope_entry_value_transfer(var, def) and + ssa_variable_defn(var, defn) + ) +} + +/* Definition for a variable (possibly) redefined by a call: + * Just assume that call does not define variable + */ +pragma [noinline] +private predicate callsite_defn(CallsiteRefinement def, Definition defn) { + ssa_variable_defn(def.getInput(), defn) +} + +/* Pass through for `self` for the implicit re-defn of `self` in `self.foo()` */ +private predicate method_callsite_defn(MethodCallsiteRefinement def, Definition defn) { + /* The value of self remains the same, only the attributes may change */ + ssa_variable_defn(def.getInput(), defn) +} + +/** Helpers for import_star_defn */ +pragma [noinline] +private predicate module_and_name_for_import_star(ModuleObject mod, string name, ImportStarRefinement def) { + exists(ImportStarNode im_star | + im_star = def.getDefiningNode() | + name = def.getSourceVariable().getName() and + im_star.getModule().refersTo(mod) and + mod.exports(name) + ) +} + +/** Holds if `def` is technically a defn of `var`, but the `from ... import *` does not in fact define `var` */ +pragma [noinline] +private predicate variable_not_redefined_by_import_star(EssaVariable var, ImportStarRefinement def) { + var = def.getInput() and + exists(ModuleObject mod | + def.getDefiningNode().(ImportStarNode).getModule().refersTo(mod) and + not mod.exports(var.getSourceVariable().getName()) + ) +} + +/* Definition for `from ... import *` */ +private predicate import_star_defn(ImportStarRefinement def, Definition defn) { + exists(ModuleObject mod, string name | + module_and_name_for_import_star(mod, name, def) | + /* Attribute from imported module */ + scope_jump_to_defn_attribute(mod.getModule(), name, defn) + ) + or + exists(EssaVariable var | + /* Retain value held before import */ + variable_not_redefined_by_import_star(var, def) and + ssa_variable_defn(var, defn) + ) +} + +/** Attribute assignments have no effect as far as defn tracking is concerned */ +private predicate attribute_assignment_defn(AttributeAssignment def, Definition defn) { + ssa_variable_defn(def.getInput(), defn) +} + +/** Ignore the effects of calls on their arguments. This is an approximation, but attempting to improve accuracy would be very expensive for very little gain. */ +private predicate argument_defn(ArgumentRefinement def, Definition defn) { + ssa_variable_defn(def.getInput(), defn) +} + +/** Attribute deletions have no effect as far as value tracking is concerned. */ +pragma [noinline] +private predicate attribute_delete_defn(EssaAttributeDeletion def, Definition defn) { + ssa_variable_defn(def.getInput(), defn) +} + +/* Definition flow for attributes. These mirror the "normal" defn predicates. + * For each defn predicate `xxx_defn(XXX def, Definition defn)` + * There is an equivalent predicate that tracks the values in attributes: + * `xxx_jump_to_defn_attribute(XXX def, string name, Definition defn)` + * */ + +/** INTERNAL -- Public for testing only. + * Holds if the attribute `name` of the ssa variable `var` refers to (`value`, `cls`, `origin`) + */ +predicate ssa_variable_jump_to_defn_attribute(EssaVariable var, string name, Definition defn) { + ssa_defn_jump_to_defn_attribute(var.getDefinition(), name, defn) +} + +/** Helper for ssa_variable_jump_to_defn_attribute */ +private predicate ssa_defn_jump_to_defn_attribute(EssaDefinition def, string name, Definition defn) { + ssa_phi_jump_to_defn_attribute(def, name, defn) + or + ssa_node_jump_to_defn_attribute(def, name, defn) + or + ssa_node_refinement_jump_to_defn_attribute(def, name, defn) + or + ssa_filter_jump_to_defn_attribute(def, name, defn) +} + +/** Holds if ESSA edge refinement, `def`, is defined by `defn` of `priority` */ +predicate ssa_filter_jump_to_defn_attribute(PyEdgeRefinement def, string name, Definition defn) { + ssa_variable_jump_to_defn_attribute(def.getInput(), name, defn) +} + +/** Holds if the attribute `name` of the ssa phi-function defn `phi` refers to (`value`, `cls`, `origin`) */ +private predicate ssa_phi_jump_to_defn_attribute(PhiFunction phi, string name, Definition defn) { + ssa_variable_jump_to_defn_attribute(phi.getAnInput(), name, defn) +} + +/** Helper for ssa_defn_jump_to_defn_attribute */ +pragma[noinline] +private predicate ssa_node_jump_to_defn_attribute(EssaNodeDefinition def, string name, Definition defn) { + assignment_jump_to_defn_attribute(def, name, defn) + or + self_parameter_jump_to_defn_attribute(def, name, defn) + or + scope_entry_jump_to_defn_attribute(def, name, defn) +} + +/** Helper for ssa_defn_jump_to_defn_attribute */ +pragma[noinline] +private predicate ssa_node_refinement_jump_to_defn_attribute(EssaNodeRefinement def, string name, Definition defn) { + attribute_assignment_jump_to_defn_attribute(def, name, defn) + or + argument_jump_to_defn_attribute(def, name, defn) +} + +pragma[noinline] +private predicate scope_entry_jump_to_defn_attribute(ScopeEntryDefinition def, string name, Definition defn) { + exists(EssaVariable var | + scope_entry_value_transfer(var, def) and + ssa_variable_jump_to_defn_attribute(var, name, defn) + ) +} + +private predicate scope_jump_to_defn_attribute(ImportTimeScope s, string name, Definition defn) { + exists(EssaVariable var | + BaseFlow::reaches_exit(var) and var.getScope() = s and + var.getName() = name + | + ssa_variable_defn(var, defn) + ) +} + +private predicate jump_to_defn_attribute(ControlFlowNode use, string name, Definition defn) { + /* Local attribute */ + exists(EssaVariable var | + use = var.getASourceUse() and + ssa_variable_jump_to_defn_attribute(var, name, defn) + ) + or + /* Instance attributes */ + exists(ClassObject cls | + use.refersTo(_, cls, _) | + scope_jump_to_defn_attribute(cls.getPyClass(), name, defn) + ) + or + /* Super attributes */ + exists(AttrNode f, SuperBoundMethod sbm, Object function | + use = f.getObject(name) and + f.refersTo(sbm) and function = sbm.getFunction(_) and + function.getOrigin() = defn.getAstNode() + ) + or + /* Class or module attribute */ + exists(Object obj, Scope scope | + use.refersTo(obj) and + scope_jump_to_defn_attribute(scope, name, defn) | + obj.(ClassObject).getPyClass() = scope + or + obj.(PythonModuleObject).getModule() = scope + or + obj.(PackageObject).getInitModule().getModule() = scope + ) +} + +pragma[noinline] +private predicate assignment_jump_to_defn_attribute(AssignmentDefinition def, string name, Definition defn) { + jump_to_defn_attribute(def.getValue(), name, defn) +} + +pragma[noinline] +private predicate attribute_assignment_jump_to_defn_attribute(AttributeAssignment def, string name, Definition defn) { + defn.getAstNode() = def.getDefiningNode().getNode() and name = def.getName() + or + ssa_variable_jump_to_defn_attribute(def.getInput(), name, defn) and not name = def.getName() +} + +/** Holds if `def` defines the attribute `name` + * `def` takes the form `setattr(use, "name")` where `use` is the input to the defn. + */ +private predicate sets_attribute(ArgumentRefinement def, string name) { + exists(CallNode call | + call = def.getDefiningNode() and + call.getFunction().refersTo(builtin_object("setattr")) and + def.getInput().getAUse() = call.getArg(0) and + call.getArg(1).getNode().(StrConst).getText() = name + ) +} + +pragma[noinline] +private predicate argument_jump_to_defn_attribute(ArgumentRefinement def, string name, Definition defn) { + if sets_attribute(def, name) then + jump_to_defn(def.getDefiningNode().(CallNode).getArg(2), defn) + else + ssa_variable_jump_to_defn_attribute(def.getInput(), name, defn) +} + +/** Gets the (temporally) preceding variable for "self", e.g. `def` is in method foo() and `result` is in `__init__()`. */ +private EssaVariable preceding_self_variable(ParameterDefinition def) { + def.isSelf() and + exists(Function preceding, Function method | + method = def.getScope() and + // Only methods + preceding.isMethod() and preceding.precedes(method) and + BaseFlow::reaches_exit(result) and result.getSourceVariable().(Variable).isSelf() and + result.getScope() = preceding + ) +} + +pragma [noinline] +private predicate self_parameter_jump_to_defn_attribute(ParameterDefinition def, string name, Definition defn) { + ssa_variable_jump_to_defn_attribute(preceding_self_variable(def), name, defn) +} + +/** Gets a definition for 'use'. + * This exists primarily for testing use `getPreferredDefinition()` instead. + */ +Definition getADefinition(Expr use) { + jump_to_defn(use.getAFlowNode(), result) and + not use instanceof Call and + not use.isArtificial() and + // Not the use itself + not result = TLocalDefinition(use) +} + +/** Gets the unique definition for 'use', if one can be found. + * Helper for the jump-to-definition query. + */ +Definition getUniqueDefinition(Expr use) { + unique_jump_to_defn(use, result) and + not use instanceof Call and + not use.isArtificial() and + // Not the use itself + not result = TLocalDefinition(use) +} + + +/** Helper class to get suitable locations for attributes */ +class NiceLocationExpr extends @py_expr { + + string toString() { + result = this.(Expr).toString() + } + + predicate hasLocationInfo(string f, int bl, int bc, int el, int ec) { + /* Attribute location for x.y is that of 'y' so that url does not overlap with that of 'x' */ + exists(int abl, int abc | + this.(Attribute).getLocation().hasLocationInfo(f, abl, abc, el, ec) | + bl = el and bc = ec - this.(Attribute).getName().length() + 1 + ) + or + this.(Name).getLocation().hasLocationInfo(f, bl, bc, el, ec) + or + /* Show xxx for `xxx` in `from xxx import y` or + * for `import xxx` or for `import xxx as yyy`. */ + this.(ImportExpr).getLocation().hasLocationInfo(f, bl, bc, el, ec) + or + /* Show y for `y` in `from xxx import y` */ + exists(string name | + name = this.(ImportMember).getName() and + this.(ImportMember).getLocation().hasLocationInfo(f, _, _, el, ec) and + bl = el and bc = ec-name.length()+1 + ) + } + +} + + diff --git a/python/ql/src/analysis/Definitions.ql b/python/ql/src/analysis/Definitions.ql new file mode 100644 index 000000000000..b0cf6f01bc1d --- /dev/null +++ b/python/ql/src/analysis/Definitions.ql @@ -0,0 +1,17 @@ +/** + * @name Definitions + * @description Jump to definition helper query. + * @kind definitions + * @id py/jump-to-definition + */ + +import python +import DefinitionTracking + + +from NiceLocationExpr use, Definition defn, string kind, string f, int l +where defn = getUniqueDefinition(use) and kind = "Definition" +and use.hasLocationInfo(f, l, _, _, _) and +// Ignore if the definition is on the same line as the use +not defn.getLocation().hasLocationInfo(f, l, _, _, _) +select use, defn, kind diff --git a/python/ql/src/analysis/Efficiency.ql b/python/ql/src/analysis/Efficiency.ql new file mode 100644 index 000000000000..bbdd3a7506da --- /dev/null +++ b/python/ql/src/analysis/Efficiency.ql @@ -0,0 +1,33 @@ +/** + * Compute the efficiency of the points-to relation. That is the ratio of + * "interesting" facts to total facts. + */ + +import python +import semmle.python.pointsto.PointsTo +import semmle.python.pointsto.PointsToContext + +predicate trivial(ControlFlowNode f) { + exists(Parameter p | p = f.getNode()) + or + f instanceof NameConstantNode + or + f.getNode() instanceof ImmutableLiteral +} + +from int interesting_facts, int interesting_facts_in_source, int total_size,float efficiency +where +interesting_facts = strictcount(ControlFlowNode f, Object value, ClassObject cls | + f.refersTo(value, cls, _) and not trivial(f) +) +and +interesting_facts_in_source = strictcount(ControlFlowNode f, Object value, ClassObject cls | + f.refersTo(value, cls, _) and not trivial(f) and exists(f.getScope().getEnclosingModule().getFile().getRelativePath()) +) +and +total_size = strictcount(ControlFlowNode f, PointsToContext ctx, Object value, ClassObject cls, ControlFlowNode orig | + PointsTo::points_to(f, ctx, value, cls, orig) +) +and +efficiency = 100.0 * interesting_facts_in_source / total_size +select interesting_facts, interesting_facts_in_source, total_size, efficiency diff --git a/python/ql/src/analysis/FailedInference.ql b/python/ql/src/analysis/FailedInference.ql new file mode 100644 index 000000000000..129c17ffd9df --- /dev/null +++ b/python/ql/src/analysis/FailedInference.ql @@ -0,0 +1,11 @@ + +import python +import semmle.python.pointsto.PointsTo + +from ClassObject cls, string reason + +where +PointsTo::Types::failed_inference(cls, reason) + +select cls, reason + diff --git a/python/ql/src/analysis/ImportFailure.qhelp b/python/ql/src/analysis/ImportFailure.qhelp new file mode 100644 index 000000000000..2832f217f3b1 --- /dev/null +++ b/python/ql/src/analysis/ImportFailure.qhelp @@ -0,0 +1,28 @@ + + + +

    Tracing which module is imported by an import statement is very important in ensuring that the whole program is available +for analysis. Failure to determine which module is imported by an import reduces the extent and accuracy of Semmle's analysis. +

    + +

    +Missing imports will degrade the effectiveness of code analysis and may result in errors going undetected. +

    + +
    + +

    +Ensure that all required modules and packages can be found when running the extractor. +

    + + +
    + + +
  • Semmle Tutorial: Basic project creation (Python).
  • + + +
    +
    diff --git a/python/ql/src/analysis/ImportFailure.ql b/python/ql/src/analysis/ImportFailure.ql new file mode 100644 index 000000000000..95fd38a37485 --- /dev/null +++ b/python/ql/src/analysis/ImportFailure.ql @@ -0,0 +1,71 @@ +/** + * @name Unresolved import + * @description An unresolved import may result in reduced coverage and accuracy of analysis. + * @kind problem + * @problem.severity info + * @id py/import-failure + */ + +import python + +ImportExpr alternative_import(ImportExpr ie) { + exists(Alias thisalias, Alias otheralias | + (thisalias.getValue() = ie or ((ImportMember)thisalias.getValue()).getModule() = ie) + and + (otheralias.getValue() = result or ((ImportMember)otheralias.getValue()).getModule() = result) + and + ( + exists(If i | i.getBody().contains(ie) and i.getOrelse().contains(result)) or + exists(If i | i.getBody().contains(result) and i.getOrelse().contains(ie)) or + exists(Try t | t.getBody().contains(ie) and t.getAHandler().contains(result)) or + exists(Try t | t.getBody().contains(result) and t.getAHandler().contains(ie)) + ) + ) +} + +string os_specific_import(ImportExpr ie) { + exists(string name | name = ie.getImportedModuleName() | + name.matches("org.python.%") and result = "java" + or + name.matches("java.%") and result = "java" + or + name.matches("Carbon.%") and result = "darwin" + or + result = "win32" and ( + name = "_winapi" or name = "_win32api" or name = "_winreg" or + name = "nt" or name.matches("win32%") or name = "ntpath" + ) + or + result = "linux2" and ( + name = "posix" or name = "posixpath" + ) + or + result = "unsupported" and ( + name = "__pypy__" or name = "ce" or name.matches("riscos%") + + ) + ) +} + +string get_os() { + py_flags_versioned("sys.platform", result, major_version().toString()) +} + +predicate ok_to_fail(ImportExpr ie) { + alternative_import(ie).refersTo(_) + or + os_specific_import(ie) != get_os() +} + +from ImportExpr ie +where not ie.refersTo(_) and + exists(Context c | c.appliesTo(ie.getAFlowNode())) and + not ok_to_fail(ie) and + not exists(VersionGuard guard | + if guard.isTrue() then + guard.controls(ie.getAFlowNode().getBasicBlock(), false) + else + guard.controls(ie.getAFlowNode().getBasicBlock(), true) + ) + +select ie, "Unable to resolve import of '" + ie.getImportedModuleName() + "'." \ No newline at end of file diff --git a/python/ql/src/analysis/KeyPointsToFailure.qhelp b/python/ql/src/analysis/KeyPointsToFailure.qhelp new file mode 100644 index 000000000000..4aaf00a6bbca --- /dev/null +++ b/python/ql/src/analysis/KeyPointsToFailure.qhelp @@ -0,0 +1,11 @@ + + + +

    Points-to analysis underpins type inference and thus most of Semmle's Python analysis. +Failures in points-to undermines type inference and reduces the coverage and also accuracy of many queries. +

    + +
    +
    diff --git a/python/ql/src/analysis/KeyPointsToFailure.ql b/python/ql/src/analysis/KeyPointsToFailure.ql new file mode 100644 index 000000000000..46b1156dd7b6 --- /dev/null +++ b/python/ql/src/analysis/KeyPointsToFailure.ql @@ -0,0 +1,31 @@ +/** + * @name Key "points-to" fails for expression. + * @description Expression does not "point-to" an object which prevents further points-to analysis. + * @kind problem + * @problem.severity info + * @id py/key-points-to-failure + */ + +import python + +predicate points_to_failure(Expr e) { + exists(ControlFlowNode f | + f = e.getAFlowNode() | + not f.refersTo(_) + ) +} + +predicate key_points_to_failure(Expr e) { + points_to_failure(e) and not points_to_failure(e.getASubExpression()) + and + not exists(SsaVariable ssa | + ssa.getAUse() = e.getAFlowNode() | + points_to_failure(ssa.getAnUltimateDefinition().getDefinition().getNode()) + ) + and + not exists(Assign a | a.getATarget() = e) +} + +from Attribute e +where key_points_to_failure(e) and not exists(Call c | c.getFunc() = e) +select e, "Expression does not 'point-to' any object, but all its sources do." diff --git a/python/ql/src/analysis/PointsToFailure.qhelp b/python/ql/src/analysis/PointsToFailure.qhelp new file mode 100644 index 000000000000..4aaf00a6bbca --- /dev/null +++ b/python/ql/src/analysis/PointsToFailure.qhelp @@ -0,0 +1,11 @@ + + + +

    Points-to analysis underpins type inference and thus most of Semmle's Python analysis. +Failures in points-to undermines type inference and reduces the coverage and also accuracy of many queries. +

    + +
    +
    diff --git a/python/ql/src/analysis/PointsToFailure.ql b/python/ql/src/analysis/PointsToFailure.ql new file mode 100644 index 000000000000..53c2296c529a --- /dev/null +++ b/python/ql/src/analysis/PointsToFailure.ql @@ -0,0 +1,18 @@ +/** + * @name "points-to" fails for expression. + * @description Expression does not "point-to" an object which prevents type inference. + * @kind problem + * @id py/points-to-failure + * @problem.severity info + * @tags reliability + */ + +import python + +from Expr e +where exists(ControlFlowNode f | + f = e.getAFlowNode() | + not f.refersTo(_) +) + +select e, "Expression does not 'point-to' any object." \ No newline at end of file diff --git a/python/ql/src/analysis/Pruned.ql b/python/ql/src/analysis/Pruned.ql new file mode 100644 index 000000000000..a40d47949e5c --- /dev/null +++ b/python/ql/src/analysis/Pruned.ql @@ -0,0 +1,13 @@ + +import python +import semmle.python.pointsto.PointsTo + +from int size + +where +size = count(ControlFlowNode f | + not PointsTo::Test::reachableBlock(f.getBasicBlock(), _) +) + + +select size diff --git a/python/ql/src/analysis/RatioOfDefinitions.ql b/python/ql/src/analysis/RatioOfDefinitions.ql new file mode 100644 index 000000000000..66e0683eab2d --- /dev/null +++ b/python/ql/src/analysis/RatioOfDefinitions.ql @@ -0,0 +1,27 @@ +/** + * @name Ratio of jump-to-definitions computed + */ + +import python + +import DefinitionTracking + +predicate want_to_have_definition(Expr e) { + /* not builtin object like len, tuple, etc. */ + not exists(Object cobj | e.refersTo(cobj) and cobj.isC()) and + ( + e instanceof Name and e.(Name).getCtx() instanceof Load + or + e instanceof Attribute and e.(Attribute).getCtx() instanceof Load + or + e instanceof ImportMember or + e instanceof ImportExpr + ) +} + +from int yes, int no +where +yes = count(Expr e | want_to_have_definition(e) and exists(getUniqueDefinition(e))) +and +no = count(Expr e | want_to_have_definition(e) and not exists(getUniqueDefinition(e))) +select yes, no, yes*100/(yes+no) + "%" diff --git a/python/ql/src/analysis/Sanity.ql b/python/ql/src/analysis/Sanity.ql new file mode 100644 index 000000000000..113f107ebc9f --- /dev/null +++ b/python/ql/src/analysis/Sanity.ql @@ -0,0 +1,228 @@ +/** + * @name Sanity check + * @description General sanity check to be run on any and all code. Should never produce any results. + * @id py/sanity-check + */ + +import python +import DefinitionTracking + +predicate uniqueness_error(int number, string what, string problem) { + ( + what = "toString" or what = "getLocation" or what = "getNode" or what = "getDefinition" or + what = "getEntryNode" or what = "getOrigin" or what = "getAnInferredType" + ) + and + ( + number = 0 and problem = "no results for " + what + "()" + or + number in [2 .. 10] and problem = number.toString() + " results for " + what + "()" + ) +} + +predicate ast_sanity(string clsname, string problem, string what) { + exists(AstNode a | + clsname = a.getAQlClass() | + uniqueness_error(count(a.toString()), "toString", problem) and what = "at " + a.getLocation().toString() or + uniqueness_error(strictcount(a.getLocation()), "getLocation", problem) and what = a.getLocation().toString() or + not exists(a.getLocation()) and problem = "no location" and what = a.toString() + ) +} + +predicate location_sanity(string clsname, string problem, string what) { + exists(Location l | + clsname = l.getAQlClass() | + uniqueness_error(count(l.toString()), "toString", problem) and what = "at " + l.toString() or + not exists(l.toString()) and problem = "no toString" and + ( + exists(AstNode thing | + thing.getLocation() = l | + what = "a location of a " + thing.getAQlClass() + ) + or + not exists(AstNode thing | thing.getLocation() = l) and + what = "a location" + ) + or + l.getEndLine() < l.getStartLine() and problem = "end line before start line" and what = "at " + l.toString() + or + l.getEndLine() = l.getStartLine() and l.getEndColumn() < l.getStartColumn() and + problem = "end column before start column" and what = "at " + l.toString() + ) +} + +predicate cfg_sanity(string clsname, string problem, string what) { + exists(ControlFlowNode f | + clsname = f.getAQlClass() | + uniqueness_error(count(f.getNode()), "getNode", problem) and what = "at " + f.getLocation().toString() or + not exists(f.getLocation()) and problem = "no location" and what = f.toString() or + uniqueness_error(count(f.(AttrNode).getObject()), "getValue", problem) and what = "at " + f.getLocation().toString() + ) +} + +predicate scope_sanity(string clsname, string problem, string what) { + exists(Scope s | + clsname = s.getAQlClass() | + uniqueness_error(count(s.getEntryNode()), "getEntryNode", problem) and what = "at " + s.getLocation().toString() or + uniqueness_error(count(s.toString()), "toString", problem) and what = "at " + s.getLocation().toString() or + uniqueness_error(strictcount(s.getLocation()), "getLocation", problem) and what = "at " + s.getLocation().toString() or + not exists(s.getLocation()) and problem = "no location" and what = s.toString() + ) +} + +string best_description_builtin_object(Object o) { + o.isBuiltin() and + ( + result = o.toString() + or + not exists(o.toString()) and py_cobjectnames(o, result) + or + not exists(o.toString()) and not py_cobjectnames(o, _) and result = "builtin object of type " + o.getAnInferredType().toString() + or + not exists(o.toString()) and not py_cobjectnames(o, _) and not exists(o.getAnInferredType().toString()) and result = "builtin object" + ) +} + +private predicate introspected_builtin_object(Object o) { + /* Only check objects from the extractor, missing data for objects generated from C source code analysis is OK. + * as it will be ignored if it doesn't match up with the introspected form. */ + py_cobject_sources(o, 0) +} + +predicate builtin_object_sanity(string clsname, string problem, string what) { + exists(Object o | + clsname = o.getAQlClass() and what = best_description_builtin_object(o) and introspected_builtin_object(o) | + not exists(o.getAnInferredType()) and not py_cobjectnames(o, _) and problem = "neither name nor type" + or + uniqueness_error(count(string name | py_cobjectnames(o, name)), "name", problem) + or + not exists(o.getAnInferredType()) and problem = "no results for getAnInferredType" + or + not exists(o.toString()) and problem = "no toString" and + not exists(string name | name.prefix(7) = "_semmle" | py_special_objects(o, name)) and + not o = unknownValue() + ) +} + +predicate source_object_sanity(string clsname, string problem, string what) { + exists(Object o | + clsname = o.getAQlClass() and not o.isBuiltin() | + uniqueness_error(count(o.getOrigin()), "getOrigin", problem) and what = "at " + o.getOrigin().getLocation().toString() + or + not exists(o.getOrigin().getLocation()) and problem = "no location" and what = "??" + or + not exists(o.toString()) and problem = "no toString" and what = "at " + o.getOrigin().getLocation().toString() + or + strictcount(o.toString()) > 1 and problem = "multiple toStrings()" and what = o.toString() + ) +} + +predicate ssa_sanity(string clsname, string problem, string what) { + /* Zero or one definitions of each SSA variable */ + exists(SsaVariable var | + clsname = var.getAQlClass() | + uniqueness_error(strictcount(var.getDefinition()), "getDefinition", problem) and what = var.getId() + ) + or + /* Dominance criterion: Definition *must* dominate *all* uses. */ + exists(SsaVariable var, ControlFlowNode defn, ControlFlowNode use | + defn = var.getDefinition() and use = var.getAUse() | + not defn.strictlyDominates(use) and not defn = use and + /* Phi nodes which share a flow node with a use come *before* the use */ + not (exists(var.getAPhiInput()) and defn = use) and + clsname = var.getAQlClass() and problem = "a definition which does not dominate a use at " + use.getLocation() and what = var.getId() + " at " + var.getLocation() + ) + or + /* Minimality of phi nodes */ + exists(SsaVariable var | + strictcount(var.getAPhiInput()) = 1 and + var.getAPhiInput().getDefinition().getBasicBlock().strictlyDominates(var.getDefinition().getBasicBlock()) + | + clsname = var.getAQlClass() and problem = " a definition which is dominated by the definition of an incoming phi edge." and what = var.getId() + " at " + var.getLocation() + ) +} + +predicate function_object_sanity(string clsname, string problem, string what) { + exists(FunctionObject func | + clsname = func.getAQlClass() | + what = func.getName() and + ( + count(func.descriptiveString()) = 0 and problem = "no descriptiveString()" + or + exists(int c | + c = strictcount(func.descriptiveString()) and c > 1 | + problem = c + "descriptiveString()s" + ) + ) + or + not exists(func.getName()) and what = "?" and problem = "no name" + ) + +} + +predicate multiple_origins_per_object(Object obj) { + not obj.isC() and not obj instanceof ModuleObject and + exists(ControlFlowNode use | strictcount(ControlFlowNode orig | use.refersTo(obj, orig)) > 1) +} + +predicate intermediate_origins(ControlFlowNode use, ControlFlowNode inter, Object obj) { + exists(ControlFlowNode orig | + not inter = orig | + use.refersTo(obj, inter) and + inter.refersTo(obj, orig) and + // It can sometimes happen that two different modules (e.g. cPickle and Pickle) + // have the same attribute, but different origins. + not strictcount(Object val | inter.(AttrNode).getObject().refersTo(val)) > 1 + ) +} + +predicate points_to_sanity(string clsname, string problem, string what) { + exists(Object obj | + multiple_origins_per_object(obj) and clsname = obj.getAQlClass() and + problem = "multiple origins for an object" and what = obj.toString() + ) + or + exists(ControlFlowNode use, ControlFlowNode inter, Object obj | + intermediate_origins(use, inter, obj) and + clsname = use.getAQlClass() and + problem = "has intermediate origin " + inter and + what = use.toString() + ) +} + +predicate jump_to_definition_sanity(string clsname, string problem, string what) { + problem = "multiple (jump-to) definitions" and + exists(Expr use | + strictcount(getUniqueDefinition(use)) > 1 and + clsname = use.getAQlClass() and + what = use.toString() + ) +} + +predicate file_sanity(string clsname, string problem, string what) { + exists(File file, Folder folder | + clsname = file.getAQlClass() and + problem = "has same name as a folder" and + what = file.getName() and + what = folder.getName() + ) or + exists(Container f | + clsname = f.getAQlClass() and + uniqueness_error(count(f.toString()), "toString", problem) and what = "file " + f.getName() + ) +} + +from string clsname, string problem, string what +where +ast_sanity(clsname, problem, what) or +location_sanity(clsname, problem, what)or +scope_sanity(clsname, problem, what) or +cfg_sanity(clsname, problem, what) or +ssa_sanity(clsname, problem, what) or +builtin_object_sanity(clsname, problem, what) or +source_object_sanity(clsname, problem, what) or +function_object_sanity(clsname, problem, what) or +points_to_sanity(clsname, problem, what) or +jump_to_definition_sanity(clsname, problem, what) or +file_sanity(clsname, problem, what) +select clsname + " " + what + " has " + problem diff --git a/python/ql/src/analysis/Summary.ql b/python/ql/src/analysis/Summary.ql new file mode 100644 index 000000000000..ba2fee0b4a89 --- /dev/null +++ b/python/ql/src/analysis/Summary.ql @@ -0,0 +1,38 @@ +/** Summarize a snapshot + */ + +import python + +from string key, string value +where +key = "Extractor version" and py_flags_versioned("extractor.version", value, _) +or +key = "Snapshot build time" and exists(date d | snapshotDate(d) and value = d.toString()) +or +key = "Interpreter version" and +exists(string major, string minor | + py_flags_versioned("version.major", major, _) and + py_flags_versioned("version.minor", minor, _) and + value = major + "." + minor +) +or +key = "Build platform" and +exists(string raw | + py_flags_versioned("sys.platform", raw, _) | + if raw = "win32" then + value = "Windows" + else if raw = "linux2" then + value = "Linux" + else if raw = "darwin" then + value = "OSX" + else + value = raw +) +or +key = "Source location" and sourceLocationPrefix(value) +or +key = "Lines of code (source)" and value = sum(ModuleMetrics m | exists(m.getFile().getRelativePath()) | m.getNumberOfLinesOfCode()).toString() +or +key = "Lines of code (total)" and value = sum(ModuleMetrics m | any() | m.getNumberOfLinesOfCode()).toString() + +select key, value diff --git a/python/ql/src/analysis/TypeHierarchyFailure.qhelp b/python/ql/src/analysis/TypeHierarchyFailure.qhelp new file mode 100644 index 000000000000..0f9083722720 --- /dev/null +++ b/python/ql/src/analysis/TypeHierarchyFailure.qhelp @@ -0,0 +1,15 @@ + + + +

    In order to analyse uses of a class, all its attributes need to be known. Without the full inheritance hierarchy this is impossible. +This is an informational query only. +

    + +

    +This is an informational query only, this query depends on points-to and type inference. +

    + +
    +
    diff --git a/python/ql/src/analysis/TypeHierarchyFailure.ql b/python/ql/src/analysis/TypeHierarchyFailure.ql new file mode 100644 index 000000000000..072eba9ac479 --- /dev/null +++ b/python/ql/src/analysis/TypeHierarchyFailure.ql @@ -0,0 +1,16 @@ +/** + * @name Inheritance hierarchy cannot be inferred for class + * @description Inability to infer inheritance hierarchy cannot be inferred for class will impair analysis + * @id py/failed-inheritance-inference + * @kind problem + * @problem.severity info + */ + +import python + + +from Class cls +where not exists(ClassObject c | c.getPyClass() = cls) +or +exists(ClassObject c | c.getPyClass() = cls | c.failedInference()) +select cls, "Inference of class hierarchy failed for class." \ No newline at end of file diff --git a/python/ql/src/analysis/TypeInferenceFailure.qhelp b/python/ql/src/analysis/TypeInferenceFailure.qhelp new file mode 100644 index 000000000000..3ca947b376d0 --- /dev/null +++ b/python/ql/src/analysis/TypeInferenceFailure.qhelp @@ -0,0 +1,13 @@ + + + + +

    +Type inference is the key part of Semmle's Python analysis. +Failures in type inference and reduces the coverage and also accuracy of many queries. +

    + +
    +
    diff --git a/python/ql/src/analysis/TypeInferenceFailure.ql b/python/ql/src/analysis/TypeInferenceFailure.ql new file mode 100644 index 000000000000..18744a1a6dad --- /dev/null +++ b/python/ql/src/analysis/TypeInferenceFailure.ql @@ -0,0 +1,14 @@ +/** + * @name Type inference fails for 'object' + * @description Type inference fails for 'object' which reduces recall for many queries. + * @kind problem + * @problem.severity info + * @id py/type-inference-failure + */ +import python + + +from ControlFlowNode f, Object o +where f.refersTo(o) and +not exists(ClassObject c | f.refersTo(o, c, _)) +select o, "Type inference fails for 'object'." \ No newline at end of file diff --git a/python/ql/src/default.qll b/python/ql/src/default.qll new file mode 100644 index 000000000000..99374e13f76a --- /dev/null +++ b/python/ql/src/default.qll @@ -0,0 +1,5 @@ +/** + * WARNING: Use of this module is DEPRECATED. + * All new queries should use `import python`. + */ +import python diff --git a/python/ql/src/external/CodeDuplication.qll b/python/ql/src/external/CodeDuplication.qll new file mode 100644 index 000000000000..7db04663ae03 --- /dev/null +++ b/python/ql/src/external/CodeDuplication.qll @@ -0,0 +1,281 @@ +/** Provides classes for detecting duplicate or similar code. */ + +import python + +/** Gets the relative path of `file`, with backslashes replaced by forward slashes. */ +private +string relativePath(File file) { + result = file.getRelativePath().replaceAll("\\", "/") +} + +/** + * Holds if the `index`-th token of block `copy` is in file `file`, spanning + * column `sc` of line `sl` to column `ec` of line `el`. + * + * For more information, see [LGTM locations](https://lgtm.com/help/ql/locations). + */ +pragma[noinline, nomagic] +private predicate tokenLocation(File file, int sl, int sc, int ec, int el, Copy copy, int index) { + file = copy.sourceFile() and + tokens(copy, index, sl, sc, ec, el) +} + +/** A token block used for detection of duplicate and similar code. */ +class Copy extends @duplication_or_similarity +{ + private + int lastToken() { + result = max(int i | tokens(this, i, _, _, _, _) | i) + } + + /** Gets the index of the token in this block starting at the location `loc`, if any. */ + int tokenStartingAt(Location loc) { + tokenLocation(loc.getFile(), loc.getStartLine(), loc.getStartColumn(), + _, _, this, result) + } + + /** Gets the index of the token in this block ending at the location `loc`, if any. */ + int tokenEndingAt(Location loc) { + tokenLocation(loc.getFile(), _, _, + loc.getEndLine(), loc.getEndColumn(), this, result) + } + + /** Gets the line on which the first token in this block starts. */ + int sourceStartLine() { + tokens(this, 0, result, _, _, _) + } + + /** Gets the column on which the first token in this block starts. */ + int sourceStartColumn() { + tokens(this, 0, _, result, _, _) + } + + /** Gets the line on which the last token in this block ends. */ + int sourceEndLine() { + tokens(this, this.lastToken(), _, _, result, _) + } + + /** Gets the column on which the last token in this block ends. */ + int sourceEndColumn() { + tokens(this, this.lastToken(), _, _, _, result) + } + + /** Gets the number of lines containing at least (part of) one token in this block. */ + int sourceLines() { + result = this.sourceEndLine() + 1 - this.sourceStartLine() + } + + /** Gets an opaque identifier for the equivalence class of this block. */ + int getEquivalenceClass() { + duplicateCode(this, _, result) or similarCode(this, _, result) + } + + /** Gets the source file in which this block appears. */ + File sourceFile() { + exists(string name | + duplicateCode(this, name, _) or similarCode(this, name, _) | + name.replaceAll("\\", "/") = relativePath(result)) + } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [LGTM locations](https://lgtm.com/help/ql/locations). + */ + predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + sourceFile().getName() = filepath and + startline = sourceStartLine() and + startcolumn = sourceStartColumn() and + endline = sourceEndLine() and + endcolumn = sourceEndColumn() + } + + /** Gets a textual representation of this element. */ + string toString() { none() } + + /** + * Gets a block that extends this one, that is, its first token is also + * covered by this block, but they are not the same block. + */ + Copy extendingBlock() { + exists(File file, int sl, int sc, int ec, int el | + tokenLocation(file, sl, sc, ec, el, this, _) and + tokenLocation(file, sl, sc, ec, el, result, 0)) and + this != result + } +} + +/** + * Holds if there is a sequence of `SimilarBlock`s `start1, ..., end1` and another sequence + * `start2, ..., end2` such that each block extends the previous one and corresponding blocks + * have the same equivalence class, with `start` being the equivalence class of `start1` and + * `start2`, and `end` the equivalence class of `end1` and `end2`. + */ +predicate similar_extension(SimilarBlock start1, SimilarBlock start2, SimilarBlock ext1, SimilarBlock ext2, int start, int ext) { + start1.getEquivalenceClass() = start and + start2.getEquivalenceClass() = start and + ext1.getEquivalenceClass() = ext and + ext2.getEquivalenceClass() = ext and + start1 != start2 and + (ext1 = start1 and ext2 = start2 or + similar_extension(start1.extendingBlock(), start2.extendingBlock(), ext1, ext2, _, ext) + ) +} + +/** + * Holds if there is a sequence of `DuplicateBlock`s `start1, ..., end1` and another sequence + * `start2, ..., end2` such that each block extends the previous one and corresponding blocks + * have the same equivalence class, with `start` being the equivalence class of `start1` and + * `start2`, and `end` the equivalence class of `end1` and `end2`. + */ +predicate duplicate_extension(DuplicateBlock start1, DuplicateBlock start2, DuplicateBlock ext1, DuplicateBlock ext2, int start, int ext) { + start1.getEquivalenceClass() = start and + start2.getEquivalenceClass() = start and + ext1.getEquivalenceClass() = ext and + ext2.getEquivalenceClass() = ext and + start1 != start2 and + (ext1 = start1 and ext2 = start2 or + duplicate_extension(start1.extendingBlock(), start2.extendingBlock(), ext1, ext2, _, ext) + ) +} + +/** A block of duplicated code. */ +class DuplicateBlock extends Copy, @duplication +{ + override string toString() { + result = "Duplicate code: " + sourceLines() + " duplicated lines." + } +} + +/** A block of similar code. */ +class SimilarBlock extends Copy, @similarity +{ + override string toString() { + result = "Similar code: " + sourceLines() + " almost duplicated lines." + } +} + +/** + * Holds if `stmt1` and `stmt2` are duplicate statements in function or toplevel `sc1` and `sc2`, + * respectively, where `scope1` and `scope2` are not the same. + */ +predicate duplicateStatement(Scope scope1, Scope scope2, Stmt stmt1, Stmt stmt2) { + exists(int equivstart, int equivend, int first, int last | + scope1.contains(stmt1) and + scope2.contains(stmt2) and + duplicateCoversStatement(equivstart, equivend, first, last, stmt1) and + duplicateCoversStatement(equivstart, equivend, first, last, stmt2) and + stmt1 != stmt2 and scope1 != scope2 + ) +} + +/** + * Holds if statement `stmt` is covered by a sequence of `DuplicateBlock`s, where `first` + * is the index of the token in the first block that starts at the beginning of `stmt`, + * while `last` is the index of the token in the last block that ends at the end of `stmt`, + * and `equivstart` and `equivend` are the equivalence classes of the first and the last + * block, respectively. + */ +private +predicate duplicateCoversStatement(int equivstart, int equivend, int first, int last, Stmt stmt) { + exists(DuplicateBlock b1, DuplicateBlock b2, Location startloc, Location endloc | + stmt.getLocation() = startloc and + stmt.getLastStatement().getLocation() = endloc and + first = b1.tokenStartingAt(startloc) and + last = b2.tokenEndingAt(endloc) and + b1.getEquivalenceClass() = equivstart and + b2.getEquivalenceClass() = equivend and + duplicate_extension(b1, _, b2, _, equivstart, equivend) + ) +} + +/** + * Holds if `sc1` is a function or toplevel with `total` lines, and `scope2` is a function or + * toplevel that has `duplicate` lines in common with `scope1`. + */ +predicate duplicateStatements(Scope scope1, Scope scope2, int duplicate, int total) { + duplicate = strictcount(Stmt stmt | duplicateStatement(scope1, scope2, stmt, _)) and + total = strictcount(Stmt stmt | scope1.contains(stmt)) +} + +/** + * Find pairs of scopes that are identical or almost identical + */ +predicate duplicateScopes(Scope s, Scope other, float percent, string message) { + exists(int total, int duplicate | + duplicateStatements(s, other, duplicate, total) | + percent = 100.0 * duplicate / total and percent >= 80.0 and + if duplicate = total then + message = "All " + total + " statements in " + s.getName() + " are identical in $@." + else + message = duplicate + " out of " + total + " statements in " + s.getName() + " are duplicated in $@." + ) +} + +/** + * Holds if `stmt1` and `stmt2` are similar statements in function or toplevel `scope1` and `scope2`, + * respectively, where `scope1` and `scope2` are not the same. + */ +private predicate similarStatement(Scope scope1, Scope scope2, Stmt stmt1, Stmt stmt2) { + exists(int start, int end, int first, int last | + scope1.contains(stmt1) and + scope2.contains(stmt2) and + similarCoversStatement(start, end, first, last, stmt1) and + similarCoversStatement(start, end, first, last, stmt2) and + stmt1 != stmt2 and scope1 != scope2 + ) +} + +/** + * Holds if statement `stmt` is covered by a sequence of `SimilarBlock`s, where `first` + * is the index of the token in the first block that starts at the beginning of `stmt`, + * while `last` is the index of the token in the last block that ends at the end of `stmt`, + * and `equivstart` and `equivend` are the equivalence classes of the first and the last + * block, respectively. + */ +private predicate similarCoversStatement(int equivstart, int equivend, int first, int last, Stmt stmt) { + exists(SimilarBlock b1, SimilarBlock b2, Location startloc, Location endloc | + stmt.getLocation() = startloc and + stmt.getLastStatement().getLocation() = endloc and + first = b1.tokenStartingAt(startloc) and + last = b2.tokenEndingAt(endloc) and + b1.getEquivalenceClass() = equivstart and + b2.getEquivalenceClass() = equivend and + similar_extension(b1, _, b2, _, equivstart, equivend) + ) +} + +/** + * Holds if `sc1` is a function or toplevel with `total` lines, and `scope2` is a function or + * toplevel that has `similar` similar lines to `scope1`. + */ +private predicate similarStatements(Scope scope1, Scope scope2, int similar, int total) { + similar = strictcount(Stmt stmt | similarStatement(scope1, scope2, stmt, _)) and + total = strictcount(Stmt stmt | scope1.contains(stmt)) +} + +/** + * Find pairs of scopes that are similar + */ +predicate similarScopes(Scope s, Scope other, float percent, string message) { + exists(int total, int similar | + similarStatements(s, other, similar, total) | + percent = 100.0 * similar / total and percent >= 80.0 and + if similar = total then + message = "All statements in " + s.getName() + " are similar in $@." + else + message = similar + " out of " + total + " statements in " + s.getName() + " are similar in $@." + ) +} + +/** + * Holds if the line is acceptable as a duplicate. + * This is true for blocks of import statements. + */ +predicate whitelistedLineForDuplication(File f, int line) { + exists(ImportingStmt i | + i.getLocation().getFile() = f and i.getLocation().getStartLine() = line + ) +} diff --git a/python/ql/src/external/DefectFilter.qll b/python/ql/src/external/DefectFilter.qll new file mode 100644 index 000000000000..9504cd085548 --- /dev/null +++ b/python/ql/src/external/DefectFilter.qll @@ -0,0 +1,67 @@ +/** Provides a class for working with defect query results stored in dashboard databases. */ + +import semmle.python.Files + +/** + * Holds if `id` is the opaque identifier of a result reported by query `queryPath`, + * such that `message` is the associated message and the location of the result spans + * column `startcol` of line `startline` to column `endcol` of line `endline` + * in file `filepath`. + * + * For more information, see [LGTM locations](https://lgtm.com/help/ql/locations). + */ +external predicate defectResults(int id, string queryPath, string filepath, int startline, + int startcol, int endline, int endcol, string message); + +/** + * A defect query result stored in a dashboard database. + */ +class DefectResult extends int { + + DefectResult() { defectResults(this, _, _, _, _, _, _, _) } + + /** Gets the path of the query that reported the result. */ + string getQueryPath() { defectResults(this, result, _, _, _, _, _, _) } + + /** Gets the file in which this query result was reported. */ + File getFile() { + exists(string path | defectResults(this, _, path, _, _, _, _, _) and result.getName() = path) + } + + /** Gets the file path in which this query result was reported. */ + string getFilePath() { defectResults(this, _, result, _, _, _, _, _) } + + /** Gets the line on which the location of this query result starts. */ + int getStartLine() { defectResults(this, _, _, result, _, _, _, _) } + + /** Gets the column on which the location of this query result starts. */ + int getStartColumn() { defectResults(this, _, _, _, result, _, _, _) } + + /** Gets the line on which the location of this query result ends. */ + int getEndLine() { defectResults(this, _, _, _, _, result, _, _) } + + /** Gets the column on which the location of this query result ends. */ + int getEndColumn() { defectResults(this, _, _, _, _, _, result, _) } + + /** Gets the message associated with this query result. */ + string getMessage() { defectResults(this, _, _, _, _, _, _, result) } + + predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + defectResults(this, _, path, sl, sc, el, ec, _) + } + + /** Gets the URL corresponding to the location of this query result. */ + string getURL() { + result = "file://" + getFile().getName() + ":" + getStartLine() + ":" + getStartColumn() + ":" + getEndLine() + ":" + getEndColumn() + } + +} + +// crude containment by line number only +predicate contains(Location l, DefectResult res) { + exists(string path, int bl1, int el1, int bl2, int el2 | + l.hasLocationInfo(path, bl1, _, el1, _) + and res.hasLocationInfo(path, bl2, _, el2, _) + and bl1 <= bl2 and el1 >= el2 + ) +} diff --git a/python/ql/src/external/DuplicateBlock.qhelp b/python/ql/src/external/DuplicateBlock.qhelp new file mode 100644 index 000000000000..b1e12aa3ef90 --- /dev/null +++ b/python/ql/src/external/DuplicateBlock.qhelp @@ -0,0 +1,31 @@ + + + + + +

    Blocks of code that are duplicated verbatim in several places in the code are candidates for +refactoring into functions. The severity of this anti-pattern is higher for longer blocks than for short blocks.

    + +
    + +

    Code duplication is undesirable for a range of reasons: The artificially +inflated amount of code hinders comprehension, and ranges of similar but subtly different lines +can mask the real purpose or intention behind a function. There's also a risk of +update anomalies, where only one of several copies of the code is updated to address a defect or +add a feature.

    + +

    In the case of code block duplication, how to address the issue depends on the blocks of code themselves. +It may be possible to extract the block of code into its own function and call that instead of duplicating the code.

    + +
    + + +
  • Elmar Juergens, Florian Deissenboeck, Benjamin Hummel, and Stefan Wagner. 2009. +Do code clones matter? In Proceedings of the 31st International Conference on +Software Engineering (ICSE '09). IEEE Computer Society, Washington, DC, USA, +485-495.
  • + +
    +
    diff --git a/python/ql/src/external/DuplicateBlock.ql b/python/ql/src/external/DuplicateBlock.ql new file mode 100644 index 000000000000..1a892b87900d --- /dev/null +++ b/python/ql/src/external/DuplicateBlock.ql @@ -0,0 +1,33 @@ +/** + * @name Duplicate code block + * @description This block of code is duplicated elsewhere. If possible, the shared code should be refactored so there is only one occurrence left. It may not always be possible to address these issues; other duplicate code checks (such as duplicate function, duplicate class) give subsets of the results with higher confidence. + * @kind problem + * @problem.severity recommendation + * @sub-severity low + * @tags testability + * maintainability + * useless-code + * duplicate-code + * statistical + * non-attributable + * @deprecated + * @precision medium + * @id py/duplicate-block + */ +import CodeDuplication + +predicate sorted_by_location(DuplicateBlock x, DuplicateBlock y) { + if x.sourceFile() = y.sourceFile() then + x.sourceStartLine() < y.sourceStartLine() + else + x.sourceFile().getName() < y.sourceFile().getName() +} + +from DuplicateBlock d, DuplicateBlock other +where d.sourceLines() > 10 and + other.getEquivalenceClass() = d.getEquivalenceClass() and + sorted_by_location(other, d) +select + d, + "Duplicate code: " + d.sourceLines() + " lines are duplicated at " + + other.sourceFile().getShortName() + ":" + other.sourceStartLine().toString() diff --git a/python/ql/src/external/DuplicateFunction.qhelp b/python/ql/src/external/DuplicateFunction.qhelp new file mode 100644 index 000000000000..c7ae7965ace7 --- /dev/null +++ b/python/ql/src/external/DuplicateFunction.qhelp @@ -0,0 +1,43 @@ + + + + + +

    A function should never be duplicated verbatim in several places in the code. Of course +the severity of this anti-pattern is higher for longer functions than for extremely short +functions of one or two statements, but there are usually better ways of achieving the same +effect.

    + +
    + +

    Code duplication in general is highly undesirable for a range of reasons: The artificially +inflated amount of code hinders comprehension, and ranges of similar but subtly different lines +can mask the real purpose or intention behind a function. There's also an omnipresent risk of +update anomalies, where only one of several copies of the code is updated to address a defect or +add a feature.

    + +

    In the case of function duplication, how to address the issue depends on the functions themselves +and on the precise classes or modules in which the duplication occurs. At its simplest, the duplication can +be addressed by simply removing all but one of the duplicate function definitions and making +callers of the removed functions refer to the (now canonical) single remaining definition +instead.

    + +

    This may not be possible for reasons of accessibility. A common example might +be where two classes implement the same functionality but neither is a subtype of the other, +so it is not possible to inherit a single method definition. In such cases, introducing a +common superclass to share the duplicated code is a viable option. Alternatively, if the methods +don't need access to private object state, they can be moved to a module-level function.

    + + +
    + + +
  • Elmar Juergens, Florian Deissenboeck, Benjamin Hummel, and Stefan Wagner. 2009. +Do code clones matter? In Proceedings of the 31st International Conference on +Software Engineering (ICSE '09). IEEE Computer Society, Washington, DC, USA, +485-495.
  • + +
    +
    diff --git a/python/ql/src/external/DuplicateFunction.ql b/python/ql/src/external/DuplicateFunction.ql new file mode 100644 index 000000000000..ddf587caf685 --- /dev/null +++ b/python/ql/src/external/DuplicateFunction.ql @@ -0,0 +1,31 @@ +/** + * @name Duplicate function + * @description There is another identical implementation of this function. Extract the code to a common file or superclass to improve sharing. + * @kind problem + * @tags testability + * useless-code + * maintainability + * duplicate-code + * statistical + * non-attributable + * @problem.severity recommendation + * @sub-severity high + * @precision high + * @id py/duplicate-function + */ +import python +import CodeDuplication + +predicate relevant(Function m) { + m.getMetrics().getNumberOfLinesOfCode() > 5 +} + +from Function m, Function other, string message, int percent +where duplicateScopes(m, other, percent, message) + and relevant(m) + and percent > 95.0 + and not duplicateScopes(m.getEnclosingModule(), other.getEnclosingModule(), _, _) + and not duplicateScopes(m.getScope(), other.getScope(), _, _) +select m, message, + other, + other.getName() diff --git a/python/ql/src/external/ExternalArtifact.qll b/python/ql/src/external/ExternalArtifact.qll new file mode 100644 index 000000000000..9c2fe6a9b668 --- /dev/null +++ b/python/ql/src/external/ExternalArtifact.qll @@ -0,0 +1,103 @@ +import python + +class ExternalDefect extends @externalDefect { + + string getQueryPath() { + exists(string path | + externalDefects(this, path, _, _, _) and + result = path.replaceAll("\\", "/") + ) + } + + string getMessage() { + externalDefects(this, _, _, result, _) + } + + float getSeverity() { + externalDefects(this, _, _, _, result) + } + + Location getLocation() { + externalDefects(this,_,result,_,_) + } + + string toString() { + result = getQueryPath() + ": " + getLocation() + " - " + getMessage() + } +} + +class ExternalMetric extends @externalMetric { + + string getQueryPath() { + externalMetrics(this, result, _, _) + } + + float getValue() { + externalMetrics(this, _, _, result) + } + + Location getLocation() { + externalMetrics(this,_,result,_) + } + + string toString() { + result = getQueryPath() + ": " + getLocation() + " - " + getValue() + } +} + +class ExternalData extends @externalDataElement { + + string getDataPath() { + externalData(this, result, _, _) + } + + string getQueryPath() { + result = getDataPath().regexpReplaceAll("\\.[^.]*$", ".ql") + } + + int getNumFields() { + result = 1 + max(int i | externalData(this, _, i, _) | i) + } + + string getField(int index) { + externalData(this, _, index, result) + } + + int getFieldAsInt(int index) { + result = getField(index).toInt() + } + + float getFieldAsFloat(int index) { + result = getField(index).toFloat() + } + + date getFieldAsDate(int index) { + result = getField(index).toDate() + } + + string toString() { + result = getQueryPath() + ": " + buildTupleString(0) + } + + private string buildTupleString(int start) { + (start = getNumFields() - 1 and result = getField(start)) + or + (start < getNumFields() - 1 and result = getField(start) + "," + buildTupleString(start+1)) + } + +} + +/** + * External data with a location, and a message, as produced by tools that used to produce QLDs. + */ +class DefectExternalData extends ExternalData { + DefectExternalData() { + this.getField(0).regexpMatch("\\w+://.*:[0-9]+:[0-9]+:[0-9]+:[0-9]+$") and + this.getNumFields() = 2 + } + + string getURL() { result = getField(0) } + + string getMessage() { result = getField(1) } +} + diff --git a/python/ql/src/external/MostlyDuplicateClass.qhelp b/python/ql/src/external/MostlyDuplicateClass.qhelp new file mode 100644 index 000000000000..e7e5a0dc50fc --- /dev/null +++ b/python/ql/src/external/MostlyDuplicateClass.qhelp @@ -0,0 +1,31 @@ + + + +

    If two classes share a lot of code then there is a lot of unnecessary code +duplication. This makes it difficult to make changes in future and makes the classes less easy to +read.

    + +
    + +

    While completely duplicated classes are rare, they are usually a sign of a simple oversight. +Usually the required action is to remove all but one of them. A common exception to this rule may +arise from generated code that simply occurs in several places in the source tree; the check can be +adapted to exclude such results.

    + +

    It is far more common to see duplication of many methods between two classes, leaving just a few +that are actually different. Consider such situations carefully. Are the differences deliberate or +a result of an inconsistent update to one of the clones? If the latter, then treating the classes +as completely duplicate and eliminating one (while preserving any corrections or new features that +may have been introduced) is the best course. If the two classes serve different purposes then it +is possible there is a missing level of abstraction. Consider creating a common superclass of the +duplicate classes.

    + +
    + + +
  • E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, Do Code Clones Matter?, 2009. (available online).
  • + +
    +
    diff --git a/python/ql/src/external/MostlyDuplicateClass.ql b/python/ql/src/external/MostlyDuplicateClass.ql new file mode 100644 index 000000000000..7a6f0b7587d7 --- /dev/null +++ b/python/ql/src/external/MostlyDuplicateClass.ql @@ -0,0 +1,24 @@ +/** + * @name Mostly duplicate class + * @description More than 80% of the methods in this class are duplicated in another class. Create a common supertype to improve code sharing. + * @kind problem + * @tags testability + * maintainability + * useless-code + * duplicate-code + * statistical + * non-attributable + * @problem.severity recommendation + * @sub-severity high + * @precision high + * @id py/mostly-duplicate-class + */ +import python +import CodeDuplication + +from Class c, Class other, string message +where duplicateScopes(c, other, _, message) + and count(c.getAStmt()) > 3 + and not duplicateScopes(c.getEnclosingModule(), _, _, _) +select c, message, other, other.getName() + diff --git a/python/ql/src/external/MostlyDuplicateFile.qhelp b/python/ql/src/external/MostlyDuplicateFile.qhelp new file mode 100644 index 000000000000..80035aef7f66 --- /dev/null +++ b/python/ql/src/external/MostlyDuplicateFile.qhelp @@ -0,0 +1,31 @@ + + + +

    If two files share a lot of code then there is a lot of unnecessary code duplication. +This makes it difficult to make changes in future and makes the code less easy to read.

    + +
    + +

    While completely duplicated files are rare, they are usually a sign of a simple oversight. +Usually the required action is to remove all but one of them. A common exception to this rule may +arise from generated code that simply occurs in several places in the source tree; the check can be +adapted to exclude such results.

    + +

    It is far more common to see duplication of many lines between two files, leaving just a few that +are actually different. Consider such situations carefully. Are the differences deliberate or a +result of an inconsistent update to one of the clones? If the latter, then treating the files as +completely duplicate and eliminating one (while preserving any corrections or new features that may +have been introduced) is the best course. If two files serve genuinely different purposes but almost +all of their lines are the same, that can be a sign that there is a missing level of abstraction. +Look for ways to share the functionality, by creating a new module for the common parts and +importing that module into the original module.

    + +
    + + +
  • E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, Do Code Clones Matter?, 2009. (available online).
  • + +
    +
    diff --git a/python/ql/src/external/MostlyDuplicateFile.ql b/python/ql/src/external/MostlyDuplicateFile.ql new file mode 100644 index 000000000000..57178d8846e0 --- /dev/null +++ b/python/ql/src/external/MostlyDuplicateFile.ql @@ -0,0 +1,21 @@ +/** + * @name Mostly duplicate module + * @description There is another file that shares a lot of the code with this file. Merge the two files to improve maintainability. + * @kind problem + * @tags testability + * maintainability + * useless-code + * duplicate-code + * statistical + * non-attributable + * @problem.severity recommendation + * @sub-severity high + * @precision high + * @id py/mostly-duplicate-file + */ +import python +import CodeDuplication + +from Module m, Module other, int percent, string message +where duplicateScopes(m, other, percent, message) +select m, message, other, other.getName() diff --git a/python/ql/src/external/MostlySimilarFile.qhelp b/python/ql/src/external/MostlySimilarFile.qhelp new file mode 100644 index 000000000000..978c8f4450e3 --- /dev/null +++ b/python/ql/src/external/MostlySimilarFile.qhelp @@ -0,0 +1,25 @@ + + + +

    This rule identifies two files that have a lot of the same lines but with different variable and +method names. This makes it difficult to make changes in future and makes the code less easy to read. +

    + +
    + +

    It is important to determine why there are small differences in the files. Sometimes the files +might have been duplicates but an update was only applied to one copy. If this is the case it should +be simple to merge the files, preserving any changes.

    + +

    If the files are intentionally different then it could be a good idea to consider extracting some +of the shared code into its own module and import that module into the original.

    + +
    + + +
  • E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, Do Code Clones Matter?, 2009. (available online).
  • + +
    +
    diff --git a/python/ql/src/external/MostlySimilarFile.ql b/python/ql/src/external/MostlySimilarFile.ql new file mode 100644 index 000000000000..4bdcce626c9d --- /dev/null +++ b/python/ql/src/external/MostlySimilarFile.ql @@ -0,0 +1,22 @@ +/** + * @name Mostly similar module + * @description There is another module that shares a lot of the code with this module. Notice that names of variables and types may have been changed. Merge the two modules to improve maintainability. + * @kind problem + * @problem.severity recommendation + * @tags testability + * maintainability + * useless-code + * duplicate-code + * statistical + * non-attributable + * @problem.severity recommendation + * @sub-severity low + * @precision high + * @id py/mostly-similar-file + */ +import python +import CodeDuplication + +from Module m, Module other, string message +where similarScopes(m, other, _, message) +select m, message, other, other.getName() diff --git a/python/ql/src/external/SimilarFunction.qhelp b/python/ql/src/external/SimilarFunction.qhelp new file mode 100644 index 000000000000..5f8d0bdb7e92 --- /dev/null +++ b/python/ql/src/external/SimilarFunction.qhelp @@ -0,0 +1,31 @@ + + + +

    If two functions share a lot of code then there is a lot of unnecessary code +duplication. This makes it difficult to make changes in future and makes the code less easy to read. +

    + +
    + +

    While completely duplicated functions are rare, they are usually a sign of a simple oversight. +Usually the required action is to remove all but one of them. A common exception to this rule may +arise from generated code that simply occurs in several places in the source tree; the check can be +adapted to exclude such results.

    + +

    It is far more common to see duplication of many lines between two functions, leaving just a few +that are actually different. Consider such situations carefully. Are the differences deliberate or a +result of an inconsistent update to one of the clones? If the latter, then treating the functions as +completely duplicate and eliminating one (while preserving any corrections or new features that may +have been introduced) is the best course. If two functions serve genuinely different purposes but +almost all of their lines are the same, then consider extracting the same lines to a separate function. +

    + +
    + + +
  • E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, Do Code Clones Matter?, 2009. (available online).
  • + +
    +
    diff --git a/python/ql/src/external/SimilarFunction.ql b/python/ql/src/external/SimilarFunction.ql new file mode 100644 index 000000000000..9d0a3f72cfb1 --- /dev/null +++ b/python/ql/src/external/SimilarFunction.ql @@ -0,0 +1,35 @@ +/** + * @name Similar function + * @description There is another function that is very similar this one. Extract the common code to a common function to improve sharing. + * @kind problem + * @tags testability + * maintainability + * useless-code + * duplicate-code + * statistical + * non-attributable + * @problem.severity recommendation + * @sub-severity low + * @precision very-high + * @id py/similar-function + */ +import python +import CodeDuplication + +predicate relevant(Function m) { + m.getMetrics().getNumberOfLinesOfCode() > 10 +} + +from Function m, Function other, string message, int percent +where similarScopes(m, other, percent, message) and + relevant(m) and + percent > 95.0 and + not duplicateScopes(m, other, _, _) and + not duplicateScopes(m.getEnclosingModule(), other.getEnclosingModule(), _, _) and + not duplicateScopes(m.getScope(), other.getScope(), _, _) +select m, message, + other, + other.getName() + + + diff --git a/python/ql/src/external/Thrift.qll b/python/ql/src/external/Thrift.qll new file mode 100644 index 000000000000..a10d5aab506c --- /dev/null +++ b/python/ql/src/external/Thrift.qll @@ -0,0 +1,320 @@ +/** + * Provides classes for working with Apache Thrift IDL files. + * This code is under development and may change without warning. + */ + + +import external.ExternalArtifact + +/** An item in the parse tree of the IDL file */ +class ThriftElement extends ExternalData { + + string kind; + + ThriftElement() { + this.getDataPath() = "thrift-" + kind + } + + string getKind() { + result = kind + } + + string getId() { + result = getField(0) + } + + int getIndex() { + result = getFieldAsInt(1) + } + + ThriftElement getParent() { + result.getId() = this.getField(2) + } + + string getValue() { + result = this.getField(3) + } + + ThriftElement getChild(int n) { + result.getIndex() = n and result.getParent() = this + } + + ThriftElement getAChild() { + result = this.getChild(_) + } + + override string toString() { + result = this.getKind() + } + + string getPath() { + result = this.getField(4) + } + + private int line() { + result = this.getFieldAsInt(5) + } + + private int column() { + result = this.getFieldAsInt(6) + } + + predicate hasLocationInfo(string fp, int bl, int bc, int el, int ec) { + fp = this.getPath() and + bl = this.line() and + bc = this.column() and + el = this.line() and + ec = this.column() + this.getValue().length()-1 + or + exists(ThriftElement first, ThriftElement last | + first = this.getChild(min(int l | exists(this.getChild(l)))) and + last = this.getChild(max(int l | exists(this.getChild(l)))) and + first.hasLocationInfo(fp, bl, bc, _, _) and + last.hasLocationInfo(fp, _, _, el, ec) + ) + } + + File getFile() { + this.hasLocationInfo(result.getAbsolutePath(), _, _, _, _) + } + +} + +abstract class ThriftNamedElement extends ThriftElement { + + abstract ThriftElement getNameElement(); + + final string getName() { + result = this.getNameElement().getValue() + } + + override string toString() { + result = this.getKind() + " " + this.getName() + or + not exists(this.getName()) and result = this.getKind() + " ???" + } + + override predicate hasLocationInfo(string fp, int bl, int bc, int el, int ec) { + exists(ThriftElement first | + first = this.getChild(min(int l | exists(this.getChild(l)))) and + first.hasLocationInfo(fp, bl, bc, _, _) and + this.getNameElement().hasLocationInfo(fp, _, _, el, ec) + ) + } + +} + +class ThriftType extends ThriftNamedElement { + + ThriftType() { + kind.matches("%type") + } + + override ThriftElement getNameElement() { + result = this.getChild(0) + or + result = this.getChild(0).(ThriftType).getNameElement() + } + + override string toString() { + result = "type " + this.getName() + } + + predicate references(ThriftStruct struct) { + this.getName() = struct.getName() and + exists(string path | + this.hasLocationInfo(path, _, _, _, _) and + struct.hasLocationInfo(path, _, _, _, _) + ) + } + +} + +/** A thrift typedef */ +class ThriftTypeDef extends ThriftNamedElement { + + ThriftTypeDef() { + kind.matches("typedef") + } + + override ThriftElement getNameElement() { + result = this.getChild(2).getChild(0) + } +} + +/** A thrift enum declaration */ +class ThriftEnum extends ThriftNamedElement { + + ThriftEnum() { + kind.matches("enum") + } + + override ThriftElement getNameElement() { + result = this.getChild(0).getChild(0) + } + +} + +/** A thrift enum field */ +class ThriftEnumField extends ThriftNamedElement { + + ThriftEnumField() { + kind.matches("enumfield") + } + + override ThriftElement getNameElement() { + result = this.getChild(0).getChild(0) + } + +} + +/** A thrift service declaration */ +class ThriftService extends ThriftNamedElement { + + ThriftService() { + kind.matches("service") + } + + override ThriftElement getNameElement() { + result = this.getChild(0).getChild(0) + } + + ThriftFunction getAFunction() { + result = this.getChild(_) + } + + ThriftFunction getFunction(string name) { + result.getName() = name and + result = this.getAFunction() + } + +} + +/** A thrift function declaration */ +class ThriftFunction extends ThriftNamedElement { + + ThriftFunction() { + kind.matches("function") + } + + override ThriftElement getNameElement() { + result = this.getChild(2).getChild(0) + } + + ThriftField getArgument(int n) { + result = this.getChild(n+3) + } + + ThriftField getAnArgument() { + result = this.getArgument(_) + } + + private ThriftThrows getAllThrows() { + result = this.getChild(_) + } + + ThriftField getAThrows() { + result = this.getAllThrows().getAChild() + } + + ThriftType getReturnType() { + result = this.getChild(1).getChild(0) + } + + override predicate hasLocationInfo(string fp, int bl, int bc, int el, int ec) { + this.getChild(1).hasLocationInfo(fp, bl, bc, _, _) and + this.getChild(2).hasLocationInfo(fp, _, _, el, ec) + } + + ThriftService getService() { + result.getAFunction() = this + } + + string getQualifiedName() { + result = this.getService().getName() + "." + this.getName() + } + +} + +class ThriftField extends ThriftNamedElement { + + ThriftField() { + kind.matches("field") + } + + override ThriftElement getNameElement() { + result = this.getChild(4) + } + + ThriftType getType() { + result = this.getChild(2) + } + +} + +class ThriftStruct extends ThriftNamedElement { + + ThriftStruct() { + kind.matches("struct") + } + + override ThriftElement getNameElement() { + result = this.getChild(0).getChild(0) + } + + ThriftField getMember(int n) { + result = this.getChild(n+1) + } + + ThriftField getAMember() { + result = this.getMember(_) + } + +} + + +class ThriftException extends ThriftNamedElement { + + ThriftException() { + kind.matches("exception") + } + + override ThriftElement getNameElement() { + result = this.getChild(0).getChild(0) + } + + ThriftField getMember(int n) { + result = this.getChild(n+1) + } + + ThriftField getAMember() { + result = this.getMember(_) + } + +} + + +class ThriftThrows extends ThriftElement { + + ThriftThrows() { + kind.matches("throws") + } + + ThriftField getAThrows() { + result = this.getChild(_) + } + +} + +/** A parse tree element that holds a primitive value */ +class ThriftValue extends ThriftElement { + + ThriftValue() { + exists(this.getValue()) + } + + override string toString() { + result = this.getKind() + " " + this.getValue() + } + +} diff --git a/python/ql/src/external/VCS.qll b/python/ql/src/external/VCS.qll new file mode 100644 index 000000000000..6b665dde5100 --- /dev/null +++ b/python/ql/src/external/VCS.qll @@ -0,0 +1,92 @@ +import python + +class Commit extends @svnentry { + + Commit() { + svnaffectedfiles(this, _, _) and + exists(date svnDate, date snapshotDate | + svnentries(this, _, _, svnDate, _) and + snapshotDate(snapshotDate) and + svnDate <= snapshotDate + ) + } + + string toString() { result = this.getRevisionName() } + + string getRevisionName() { svnentries(this, result, _, _, _) } + + string getAuthor() { svnentries(this, _, result, _, _) } + + date getDate() { svnentries(this, _, _, result, _) } + + int getChangeSize() { svnentries(this, _, _, _, result) } + + string getMessage() { svnentrymsg(this, result) } + + string getAnAffectedFilePath(string action) { + exists(File rawFile | svnaffectedfiles(this, rawFile, action) | + result = rawFile.getName() + ) + } + + string getAnAffectedFilePath() { result = getAnAffectedFilePath(_) } + + File getAnAffectedFile(string action) { + svnaffectedfiles(this,result,action) + } + + File getAnAffectedFile() { exists(string action | result = this.getAnAffectedFile(action)) } + + predicate isRecent() { recentCommit(this) } + + int daysToNow() { + exists(date now | snapshotDate(now) | + result = getDate().daysTo(now) and result >= 0 + ) + } + + int getRecentAdditionsForFile(File f) { + svnchurn(this, f, result, _) + } + + int getRecentDeletionsForFile(File f) { + svnchurn(this, f, _, result) + } + + int getRecentChurnForFile(File f) { + result = getRecentAdditionsForFile(f) + getRecentDeletionsForFile(f) + } + +} + +class Author extends string { + Author() { exists(Commit e | this = e.getAuthor()) } + + Commit getACommit() { result.getAuthor() = this } + + File getAnEditedFile() { result = this.getACommit().getAnAffectedFile() } + +} + +predicate recentCommit(Commit e) { + exists(date snapshotDate, date commitDate, int days | + snapshotDate(snapshotDate) and + e.getDate() = commitDate and + days = commitDate.daysTo(snapshotDate) and + days >= 0 and days <= 60 + ) +} + +date firstChange(File f) { + result = min(Commit e, date toMin | (f = e.getAnAffectedFile()) and (toMin = e.getDate()) | toMin) +} + +predicate firstCommit(Commit e) { + not exists(File f | f = e.getAnAffectedFile() | + firstChange(f) < e.getDate() + ) +} + +predicate artificialChange(Commit e) { + firstCommit(e) or e.getChangeSize() >= 50000 +} \ No newline at end of file diff --git a/python/ql/src/python.qll b/python/ql/src/python.qll new file mode 100644 index 000000000000..cdf33c8019f9 --- /dev/null +++ b/python/ql/src/python.qll @@ -0,0 +1,40 @@ +import semmle.python.Files +import semmle.python.Operations +import semmle.python.Variables +import semmle.python.AstGenerated +import semmle.python.AstExtended +import semmle.python.AST +import semmle.python.Function +import semmle.python.Module +import semmle.python.Class +import semmle.python.Import +import semmle.python.Stmts +import semmle.python.Exprs +import semmle.python.Keywords +import semmle.python.Comprehensions +import semmle.python.Lists +import semmle.python.Flow +import semmle.python.Metrics +import semmle.python.Constants +import semmle.python.Scope +import semmle.python.Comment +import semmle.python.GuardedControlFlow +import semmle.python.types.ImportTime +import semmle.python.types.Object +import semmle.python.types.ClassObject +import semmle.python.types.FunctionObject +import semmle.python.types.ModuleObject +import semmle.python.types.Version +import semmle.python.types.Descriptors +import semmle.python.protocols +import semmle.python.SSA +import semmle.python.Assigns +import semmle.python.SelfAttribute +import semmle.python.types.Properties +import semmle.python.xml.XML +import semmle.dataflow.SSA +import semmle.python.pointsto.Base +import semmle.python.pointsto.Context +import semmle.python.pointsto.CallGraph + +import site diff --git a/python/ql/src/queries.xml b/python/ql/src/queries.xml new file mode 100644 index 000000000000..27449f34263b --- /dev/null +++ b/python/ql/src/queries.xml @@ -0,0 +1 @@ + diff --git a/python/ql/src/semmle/crypto/Crypto.qll b/python/ql/src/semmle/crypto/Crypto.qll new file mode 100644 index 000000000000..12e81a393cee --- /dev/null +++ b/python/ql/src/semmle/crypto/Crypto.qll @@ -0,0 +1,202 @@ +/** + * Provides classes for modeling cryptographic libraries. + */ + +/* The following information is copied from `/semmlecode-javascript-queries/semmle/javascript/frameworks/CryptoLibraries.qll` + * which should be considered the definitive version (as of Feb 2018) + */ + + +/** + * Names of cryptographic algorithms, separated into strong and weak variants. + * + * The names are normalized: upper-case, no spaces, dashes or underscores. + * + * The names are inspired by the names used in real world crypto libraries. + * + */ +private module AlgorithmNames { + predicate isStrongHashingAlgorithm(string name) { + name = "DSA" or + name = "ED25519" or + name = "ES256" or name = "ECDSA256" or + name = "ES384" or name = "ECDSA384" or + name = "ES512" or name = "ECDSA512" or + name = "SHA2" or + name = "SHA224" or + name = "SHA256" or + name = "SHA384" or + name = "SHA512" or + name = "SHA3" + } + + predicate isWeakHashingAlgorithm(string name) { + name = "HAVEL128" or + name = "MD2" or + name = "MD4" or + name = "MD5" or + name = "PANAMA" or + name = "RIPEMD" or + name = "RIPEMD128" or + name = "RIPEMD256" or + name = "RIPEMD160" or + name = "RIPEMD320" or + name = "SHA0" or + name = "SHA1" + } + + predicate isStrongEncryptionAlgorithm(string name) { + name = "AES" or + name = "AES128" or + name = "AES192" or + name = "AES256" or + name = "AES512" or + name = "RSA" or + name = "RABBIT" or + name = "BLOWFISH" + + } + + predicate isWeakEncryptionAlgorithm(string name) { + name = "DES" or + name = "3DES" or name = "TRIPLEDES" or name = "TDEA" or name = "TRIPLEDEA" or + name = "ARC2" or name = "RC2" or + name = "ARC4" or name = "RC4" or name = "ARCFOUR" or + name = "ARC5" or name = "RC5" + } + + predicate isStrongPasswordHashingAlgorithm(string name) { + name = "ARGON2" or + name = "PBKDF2" or + name = "BCRYPT" or + name = "SCRYPT" + } + + predicate isWeakPasswordHashingAlgorithm(string name) { + none() + } + + /** + * Normalizes `name`: upper-case, no spaces, dashes or underscores. + * + * All names of this module are in this normalized form. + */ + bindingset[name] string normalizeName(string name) { + result = name.toUpperCase().regexpReplaceAll("[-_ ]", "") + } + +} +private import AlgorithmNames + + +/** + * A cryptographic algorithm. + */ +private newtype TCryptographicAlgorithm = +MkHashingAlgorithm(string name, boolean isWeak) { + (isStrongHashingAlgorithm(name) and isWeak = false) or + (isWeakHashingAlgorithm(name) and isWeak = true) +} +or +MkEncryptionAlgorithm(string name, boolean isWeak) { + (isStrongEncryptionAlgorithm(name) and isWeak = false) or + (isWeakEncryptionAlgorithm(name) and isWeak = true) +} +or +MkPasswordHashingAlgorithm(string name, boolean isWeak) { + (isStrongPasswordHashingAlgorithm(name) and isWeak = false) or + (isWeakPasswordHashingAlgorithm(name) and isWeak = true) +} + +/** + * A cryptographic algorithm. + */ +abstract class CryptographicAlgorithm extends TCryptographicAlgorithm { + + /** Gets a textual representation of this element. */ + string toString() { + result = getName() + } + + /** + * Gets the name of the algorithm. + */ + abstract string getName(); + + /** + * Holds if this algorithm is weak. + */ + abstract predicate isWeak(); + +} + +/** + * A hashing algorithm such as `MD5` or `SHA512`. + */ +class HashingAlgorithm extends MkHashingAlgorithm, CryptographicAlgorithm { + + string name; + + boolean isWeak; + + HashingAlgorithm() { + this = MkHashingAlgorithm(name, isWeak) + } + + override string getName() { + result = name + } + + override predicate isWeak() { + isWeak = true + } + +} + +/** + * An encryption algorithm such as `DES` or `AES512`. + */ +class EncryptionAlgorithm extends MkEncryptionAlgorithm, CryptographicAlgorithm { + + string name; + + boolean isWeak; + + EncryptionAlgorithm() { + this = MkEncryptionAlgorithm(name, isWeak) + } + + override string getName() { + result = name + } + + override predicate isWeak() { + isWeak = true + } + +} + +/** + * A password hashing algorithm such as `PBKDF2` or `SCRYPT`. + */ +class PasswordHashingAlgorithm extends MkPasswordHashingAlgorithm, CryptographicAlgorithm { + + string name; + + boolean isWeak; + + PasswordHashingAlgorithm() { + this = MkPasswordHashingAlgorithm(name, isWeak) + } + + override string getName() { + result = name + } + + override predicate isWeak() { + isWeak = true + } +} + + + diff --git a/python/ql/src/semmle/dataflow/SSA.qll b/python/ql/src/semmle/dataflow/SSA.qll new file mode 100755 index 000000000000..0338b6a2e0a4 --- /dev/null +++ b/python/ql/src/semmle/dataflow/SSA.qll @@ -0,0 +1,554 @@ +/** + * Library for SSA representation (Static Single Assignment form). + */ + +import python +private import SsaCompute + +/* The general intent of this code is to assume only the following interfaces, + * although several Python-specific parts may have crept in. + * + * SsaSourceVariable { ... } // See interface below + * + * + * BasicBlock { + * + * ControlFlowNode getNode(int n); + * + * BasicBlock getImmediateDominator(); + * + * BasicBlock getAPredecessor(); + * + * BasicBlock getATrueSuccessor(); + * + * BasicBlock getAFalseSuccessor(); + * + * predicate dominanceFrontier(BasicBlock other); + * + * predicate strictlyDominates(BasicBlock other); + * + * predicate hasLocationInfo(string f, int bl, int bc, int el, int ec); + * + * } + * + * ControlFlowNode { + * + * Location getLocation(); + * + * BasicBlock getBasicBlock(); + * + * } + * + */ + + + /** A source language variable, to be converted into a set of SSA variables. */ +abstract class SsaSourceVariable extends @py_variable { + + /** Gets the name of this variable */ + abstract string getName(); + + string toString() { + result = "SsaSourceVariable " + this.getName() + } + + /** Gets a use of this variable, either explicit or implicit. */ + abstract ControlFlowNode getAUse(); + + /** Holds if `def` defines an ESSA variable for this variable. */ + abstract predicate hasDefiningNode(ControlFlowNode def); + + /** Holds if the edge `pred`->`succ` defines an ESSA variable for this variable. */ + abstract predicate hasDefiningEdge(BasicBlock pred, BasicBlock succ); + + /** Holds if `def` defines an ESSA variable for this variable in such a way + * that the new variable is a refinement in some way of the variable used at `use`. + */ + abstract predicate hasRefinement(ControlFlowNode use, ControlFlowNode def); + + /** Holds if the edge `pred`->`succ` defines an ESSA variable for this variable in such a way + * that the new variable is a refinement in some way of the variable used at `use`. + */ + abstract predicate hasRefinementEdge(ControlFlowNode use, BasicBlock pred, BasicBlock succ); + + /** Gets a use of this variable that corresponds to an explicit use in the source. */ + abstract ControlFlowNode getASourceUse(); + +} + +/** An (enhanced) SSA variable derived from `SsaSourceVariable`. */ +class EssaVariable extends TEssaDefinition { + + /** Gets the (unique) definition of this variable. */ + EssaDefinition getDefinition() { + this = result + } + + /** Gets a use of this variable, where a "use" is defined by + * `SsaSourceVariable.getAUse()`. + * Note that this differs from `EssaVariable.getASourceUse()`. + */ + ControlFlowNode getAUse() { + result = this.getDefinition().getAUse() + } + + /** Gets the source variable from which this variable is derived. */ + SsaSourceVariable getSourceVariable() { + result = this.getDefinition().getSourceVariable() + } + + /** Gets the name of this variable. */ + string getName() { + result = this.getSourceVariable().getName() + } + + string toString() { + result = "SSA variable " + this.getName() + } + + /** Gets a string representation of this variable. + * WARNING: The format of this may change and it may be very inefficient to compute. + * To used for debugging and testing only. + */ + string getRepresentation() { + result = this.getSourceVariable().getName() + "_" + var_rank(this) + } + + /** Gets a use of this variable, where a "use" is defined by + * `SsaSourceVariable.getASourceUse()`. + * Note that this differs from `EssaVariable.getAUse()`. + */ + ControlFlowNode getASourceUse() { + result = this.getAUse() and + result = this.getSourceVariable().getASourceUse() + } + + /** Gets the scope of this variable. */ + Scope getScope() { + result = this.getDefinition().getScope() + } + +} + +/* Helper for location_string + * NOTE: This is Python specific, to make `getRepresentation()` portable will require further work. + */ +private int exception_handling(BasicBlock b) { + b.reachesExit() and result = 0 + or + not b.reachesExit() and result = 1 +} + +/* Helper for var_index. Come up with a (probably) unique string per location. */ +pragma[noinline] +private string location_string(EssaVariable v) { + exists(EssaDefinition def, BasicBlock b, int index, int line, int col | + def = v.getDefinition() and + (if b.getNode(0).isNormalExit() then + line = 100000 and col = 0 + else + b.hasLocationInfo(_, line, col, _, _) + ) and + /* Add large numbers to values to prevent 1000 sorting before 99 */ + result = (line + 100000) + ":" + (col*2 + 10000 + exception_handling(b)) + ":" + (index + 100003) + | + def = TEssaNodeDefinition(_, b, index) + or + def = TEssaEdgeDefinition(_, _, b) and index = piIndex() + or + def = TPhiFunction(_, b) and index = phiIndex() + ) +} + +/* Helper to compute an index for this SSA variable. */ +private int var_index(EssaVariable v) { + location_string(v) = rank[result](string s | exists(EssaVariable x | location_string(x) = s) | s) +} + +/* Helper for `v.getRepresentation()` */ +private int var_rank(EssaVariable v) { + exists(int r, SsaSourceVariable var | + var = v.getSourceVariable() and + var_index(v) = rank[r](EssaVariable x | x.getSourceVariable() = var | var_index(x)) and + result = r-1 + ) +} + +/** Underlying IPA type for EssaDefinition and EssaVariable. */ +private cached newtype TEssaDefinition = + TEssaNodeDefinition(SsaSourceVariable v, BasicBlock b, int i) { + EssaDefinitions::variableUpdate(v, _, b, _, i) + } + or + TEssaEdgeDefinition(SsaSourceVariable v, BasicBlock pred, BasicBlock succ) { + EssaDefinitions::piNode(v, pred, succ) + } + or + TPhiFunction(SsaSourceVariable v, BasicBlock b) { + EssaDefinitions::phiNode(v, b) + } + +/** Definition of an extended-SSA (ESSA) variable. + * There is exactly one definition for each variable, + * and exactly one variable for each definition. + */ +abstract class EssaDefinition extends TEssaDefinition { + + string toString() { + result = "EssaDefinition" + } + + /** Gets the source variable for which this a definition, either explicit or implicit. */ + abstract SsaSourceVariable getSourceVariable(); + + /** Gets a use of this definition as defined by the `SsaSourceVariable` class. */ + abstract ControlFlowNode getAUse(); + + /** Holds if this definition reaches the end of `b`. */ + abstract predicate reachesEndOfBlock(BasicBlock b); + + /** Gets the location of a control flow node that is indicative of this definition. + * Since definitions may occur on edges of the control flow graph, the given location may + * be imprecise. + * Distinct `EssaDefinitions` may return the same ControlFlowNode even for + * the same variable. + */ + abstract Location getLocation(); + + /** Gets a representation of this SSA definition for debugging purposes. + * Since this is primarily for debugging and testing, performance may be poor. */ + abstract string getRepresentation(); + + abstract Scope getScope(); + + EssaVariable getVariable() { + result.getDefinition() = this + } + +} + +/** An ESSA definition corresponding to an edge refinement of the underlying variable. + * For example, the edges leaving a test on a variable both represent refinements of that + * variable. On one edge the test is true, on the other it is false. + */ +class EssaEdgeRefinement extends EssaDefinition, TEssaEdgeDefinition { + + override string toString() { + result = "SSA filter definition" + } + + boolean getSense() { + this.getPredecessor().getATrueSuccessor() = this.getSuccessor() and result = true + or + this.getPredecessor().getAFalseSuccessor() = this.getSuccessor() and result = false + } + + override SsaSourceVariable getSourceVariable() { + this = TEssaEdgeDefinition(result, _, _) + } + + /** Gets the basic block preceding the edge on which this refinement occurs. */ + BasicBlock getPredecessor() { + this = TEssaEdgeDefinition(_, result, _) + } + + /** Gets the basic block succeeding the edge on which this refinement occurs. */ + BasicBlock getSuccessor() { + this = TEssaEdgeDefinition(_, _, result) + } + + override ControlFlowNode getAUse() { + SsaDefinitions::reachesUse(this.getSourceVariable(), this.getSuccessor(), piIndex(), result) + } + + override predicate reachesEndOfBlock(BasicBlock b) { + SsaDefinitions::reachesEndOfBlock(this.getSourceVariable(), this.getSuccessor(), piIndex(), b) + } + + override Location getLocation() { + result = this.getSuccessor().getNode(0).getLocation() + } + + /** Gets the SSA variable to which this refinement applies. */ + EssaVariable getInput() { + exists(SsaSourceVariable var , EssaDefinition def | + var = this.getSourceVariable() and + var = def.getSourceVariable() and + def.reachesEndOfBlock(this.getPredecessor()) and + result.getDefinition() = def + ) + } + + override string getRepresentation() { + result = this.getAQlClass() + "(" + this.getInput().getRepresentation() + ")" + } + + /** Gets the scope of the variable defined by this definition. */ + override Scope getScope() { + result = this.getPredecessor().getScope() + } + +} + +/** A Phi-function as specified in classic SSA form. */ +class PhiFunction extends EssaDefinition, TPhiFunction { + + override ControlFlowNode getAUse() { + SsaDefinitions::reachesUse(this.getSourceVariable(), this.getBasicBlock(), phiIndex(), result) + } + + override predicate reachesEndOfBlock(BasicBlock b) { + SsaDefinitions::reachesEndOfBlock(this.getSourceVariable(), this.getBasicBlock(), phiIndex(), b) + } + + override SsaSourceVariable getSourceVariable() { + this = TPhiFunction(result, _) + } + + /** Gets an input refinement that exists on one of the incoming edges to this phi node. */ + private EssaEdgeRefinement inputEdgeRefinement(BasicBlock pred) { + result.getSourceVariable() = this.getSourceVariable() and + result.getSuccessor() = this.getBasicBlock() and + result.getPredecessor() = pred + } + + private BasicBlock nonPiInput() { + result = this.getBasicBlock().getAPredecessor() and + not exists(this.inputEdgeRefinement(result)) + } + + /** Gets another definition of the same source variable that reaches this definition. */ + private EssaDefinition reachingDefinition(BasicBlock pred) { + result.getScope() = this.getScope() and + result.getSourceVariable() = this.getSourceVariable() and + pred = this.nonPiInput() and + result.reachesEndOfBlock(pred) + } + + /** Gets the input variable for this phi node on the edge `pred` -> `this.getBasicBlock()`, if any. */ + pragma [noinline] + EssaVariable getInput(BasicBlock pred) { + result.getDefinition() = this.reachingDefinition(pred) + or + result.getDefinition() = this.inputEdgeRefinement(pred) + } + + /** Gets an input variable for this phi node. */ + EssaVariable getAnInput() { + result = this.getInput(_) + } + + /** Holds if forall incoming edges in the flow graph, there is an input variable */ + predicate isComplete() { + forall(BasicBlock pred | + pred = this.getBasicBlock().getAPredecessor() | + exists(this.getInput(pred)) + ) + } + + override string toString() { + result = "SSA Phi Function" + } + + /** Gets the basic block that succeeds this phi node. */ + BasicBlock getBasicBlock() { + this = TPhiFunction(_, result) + } + + override Location getLocation() { + result = this.getBasicBlock().getNode(0).getLocation() + } + + /** Helper for `argList(n)`. */ + private int rankInput(EssaVariable input) { + input = this.getAnInput() and + var_index(input) = rank[result](EssaVariable v | v = this.getAnInput() | var_index(v)) + } + + /** Helper for `argList()`. */ + private string argList(int n) { + exists(EssaVariable input | + n = this.rankInput(input) + | + n = 1 and result = input.getRepresentation() + or + n > 1 and result = this.argList(n-1) + ", " + input.getRepresentation() + ) + } + + /** Helper for `getRepresentation()`. */ + private string argList() { + exists(int last | + last = (max(int x | x = this.rankInput(_))) and + result = this.argList(last) + ) + } + + override string getRepresentation() { + not exists(this.getAnInput()) and result = "phi()" + or + result = "phi(" + this.argList() + ")" + or + exists(this.getAnInput()) and not exists(this.argList()) and + result = "phi(" + this.getSourceVariable().getName() + "??)" + } + + override Scope getScope() { + result = this.getBasicBlock().getScope() + } + + private EssaEdgeRefinement piInputDefinition(EssaVariable input) { + input = this.getAnInput() and + result = input.getDefinition() + or + input = this.getAnInput() and result = input.getDefinition().(PhiFunction).piInputDefinition(_) + } + + /** Gets the variable which is the common and complete input to all pi-nodes that are themselves + * inputs to this phi-node. + * For example: + * ``` + * x = y() + * if complicated_test(x): + * do_a() + * else: + * do_b() + * phi + * ``` + * Which gives us the ESSA form: + * x0 = y() + * x1 = pi(x0, complicated_test(x0)) + * x2 = pi(x0, not complicated_test(x0)) + * x3 = phi(x1, x2) + * However we may not be able to track the value of `x` through `compilated_test` + * meaning that we cannot track `x` from `x0` to `x3`. + * By using `getShortCircuitInput()` we can do so, since the short-circuit input of `x3` is `x0`. + */ + pragma [noinline] + EssaVariable getShortCircuitInput() { + exists(BasicBlock common | + forall(EssaVariable input | + input = this.getAnInput() | + common = this.piInputDefinition(input).getPredecessor() + ) + and + forall(BasicBlock succ | + succ = common.getASuccessor() | + succ = this.piInputDefinition(_).getSuccessor() + ) + and + exists(EssaEdgeRefinement ref | + ref = this.piInputDefinition(_) and + ref.getPredecessor() = common and + ref.getInput() = result + ) + ) + } +} + +library class EssaNode extends EssaDefinition, TEssaNodeDefinition { + + override string toString() { + result = "Essa node definition" + } + + /** Gets the ControlFlowNode corresponding to this definition */ + ControlFlowNode getDefiningNode() { + this.definedBy(_, result) + } + + override Location getLocation() { + result = this.getDefiningNode().getLocation() + } + + override ControlFlowNode getAUse() { + exists(SsaSourceVariable v, BasicBlock b, int i | + this = TEssaNodeDefinition(v, b, i) and + SsaDefinitions::reachesUse(v, b, i, result) + ) + } + + override predicate reachesEndOfBlock(BasicBlock b) { + exists(BasicBlock defb, int i | + this = TEssaNodeDefinition(_, defb, i) and + SsaDefinitions::reachesEndOfBlock(this.getSourceVariable(), defb, i, b) + ) + } + + override SsaSourceVariable getSourceVariable() { + this = TEssaNodeDefinition(result, _, _) + } + + override string getRepresentation() { + result = this.getDefiningNode().toString() + } + + override Scope getScope() { + exists(BasicBlock defb | + this = TEssaNodeDefinition(_, defb, _) and + result = defb.getScope() + ) + } + + predicate definedBy(SsaSourceVariable v, ControlFlowNode def) { + exists(BasicBlock b, int i | + def = b.getNode(i) | + this = TEssaNodeDefinition(v, b, i+i) + or + this = TEssaNodeDefinition(v, b, i+i+1) + ) + } + +} + +/** A definition of an ESSA variable that is not directly linked to + * another ESSA variable. + */ +class EssaNodeDefinition extends EssaNode { + + EssaNodeDefinition() { + this.getSourceVariable().hasDefiningNode(this.getDefiningNode()) + } + +} + +/** A definition of an ESSA variable that takes another ESSA variable as an input. + */ +class EssaNodeRefinement extends EssaNode { + + EssaNodeRefinement() { + exists(SsaSourceVariable v, ControlFlowNode def | + this.definedBy(v, def) and + v.hasRefinement(_, def) + ) + } + + override string toString() { + result = "SSA filter definition" + } + + /** Gets the SSA variable to which this refinement applies. */ + EssaVariable getInput() { + result = potential_input(this) and + not result = potential_input(potential_input(this).getDefinition()) + } + + override string getRepresentation() { + result = this.getAQlClass() + "(" + this.getInput().getRepresentation() + ")" + } + +} + +pragma[noopt] +private EssaVariable potential_input(EssaNodeRefinement ref) { + exists(EssaNode node, ControlFlowNode use, SsaSourceVariable var, ControlFlowNode def | + var.hasRefinement(use, def) and + use = result.getAUse() and + var = result.getSourceVariable() and + def = node.getDefiningNode() and + var = node.getSourceVariable() and + ref = (EssaNodeRefinement)node + ) +} + + diff --git a/python/ql/src/semmle/dataflow/SsaCompute.qll b/python/ql/src/semmle/dataflow/SsaCompute.qll new file mode 100644 index 000000000000..dbdd51e115c9 --- /dev/null +++ b/python/ql/src/semmle/dataflow/SsaCompute.qll @@ -0,0 +1,314 @@ +/** Provides predicates for computing Enhanced SSA form + * Computation of ESSA form is identical to plain SSA form, + * but what counts as a use of definition differs. + * + * ## Language independent data-flow graph construction + * + * Construction of the data-flow graph is based on the principles behind SSA variables. + * + * The definition of an SSA variable is that (statically): + * + * * Each variable has exactly one definition + * * A variable's definition dominates all its uses. + * + * SSA form was originally designed for compiler use and thus a "definition" of an SSA variable is + * the same as a definition of the underlying source-code variable. For register allocation this is + * sufficient to treat the variable as equivalent to the value held in the variable. + * + * However, this doesn't always work the way we want it for data-flow analysis. + * + * When we start to consider attribute assignment, tests on the value referred to be a variable, + * escaping variables, implicit definitions, etc., we need something finer grained. + * + * A data-flow variable has the same properties as a normal SSA variable, but it also has the property that + * *anything* that may change the way we view an object referred to by a variable should be treated as a definition of that variable. + * + * For example, tests are treated as definitions, so for the following Python code: + * ```python + * x = None + * if not x: + * x = True + * ``` + * The data-flow graph (for `x`) is: + * ``` + * x0 = None + * x1 = pi(x0, not x) + * x2 = True + * x3 = phi(x1, x2) + * ``` + * from which is it possible to infer that `x3` may not be None. + * [ Phi functions are standard SSA, a Pi function is a filter or guard on the possible values that a variable + * may hold] + * + * Attribute assignments are also treated as definitions, so for the following Python code: + * ```python + * x = C() + * x.a = 1 + * y = C() + * y.b = 2 + * ``` + * The data-flow graph is: + * ``` + * x0 = C() + * x1 = attr-assign(x0, .a = 1) + * y0 = C() + * y1 = attr-assign(y0, .b = 1) + * ``` + * From which we can infer that `x1.a` is `1` but we know nothing about `y0.a` despite it being the same type. + * + * We can also insert "definitions" for transfers of values (say in global variables) where we do not yet know the call-graph. For example, + * ```python + * def foo(): + * global g + * g = 1 + * + * def bar(): + * foo() + * g + * ``` + * It should be clear in the above code that the use of `g` will have a value of `1`. + * The data-flow graph looks like: + * ```python + * def foo(): + * g0 = scope-entry(g) + * g1 = 1 + * + * def bar(): + * g2 = scope-entry(g) + * foo() + * g3 = call-site(g, foo()) + * ``` + * Once we have established that `foo()` calls `foo`, then it is possible to link `call-site(g, foo())` to the final value of `g` in `foo`, i.e. `g1`, so effectively `g3 = call-site(g, foo())` becomes `g3 = g1` and the global data-flow graph for `g` effectively becomes: + * ``` + * g0 = scope-entry(g) + * g1 = 1 + * g2 = scope-entry(g) + * g3 = g1 + * ``` + * and thus it falls out that `g3` must be `1`. + * + */ + + +import python +import semmle.dataflow.SSA + + +private cached module SsaComputeImpl { + + cached module EssaDefinitionsImpl { + + /** Whether `n` is a live update that is a definition of the variable `v`. */ + cached predicate variableUpdate(SsaSourceVariable v, ControlFlowNode n, BasicBlock b, int rankix, int i) { + SsaComputeImpl::variableDef(v, n, b, i) and + SsaComputeImpl::defUseRank(v, b, rankix, i) and + ( + SsaComputeImpl::defUseRank(v, b, rankix+1, _) and not SsaComputeImpl::defRank(v, b, rankix+1, _) + or + not SsaComputeImpl::defUseRank(v, b, rankix+1, _) and Liveness::liveAtExit(v, b) + ) + } + + /** Holds if `def` is a pi-node for `v` on the edge `pred` -> `succ` */ + cached predicate piNode(SsaSourceVariable v, BasicBlock pred, BasicBlock succ) { + v.hasRefinementEdge(_, pred, succ) and + Liveness::liveAtEntry(v, succ) + } + + /** A phi node for `v` at the beginning of basic block `b`. */ + cached predicate phiNode(SsaSourceVariable v, BasicBlock b) { + ( + exists(BasicBlock def | def.dominanceFrontier(b) | + SsaComputeImpl::ssaDef(v, def) + ) + or + piNode(v, _, b) and strictcount(b.getAPredecessor()) > 1 + ) and + Liveness::liveAtEntry(v, b) + } + } + + cached predicate variableDef(SsaSourceVariable v, ControlFlowNode n, BasicBlock b, int i) { + (v.hasDefiningNode(n) or v.hasRefinement(_, n)) + and + exists(int j | + n = b.getNode(j) and + i = j*2 + 1 + ) + } + + /** + * A ranking of the indices `i` at which there is an SSA definition or use of + * `v` in the basic block `b`. + * + * Basic block indices are translated to rank indices in order to skip + * irrelevant indices at which there is no definition or use when traversing + * basic blocks. + */ + cached predicate defUseRank(SsaSourceVariable v, BasicBlock b, int rankix, int i) { + i = rank[rankix](int j | variableDef(v, _, b, j) or variableUse(v, _, b, j)) + } + + /** A definition of a variable occurring at the specified rank index in basic block `b`. */ + cached predicate defRank(SsaSourceVariable v, BasicBlock b, int rankix, int i) { + variableDef(v, _, b, i) and + defUseRank(v, b, rankix, i) + } + + /** A `VarAccess` `use` of `v` in `b` at index `i`. */ + cached predicate variableUse(SsaSourceVariable v, ControlFlowNode use, BasicBlock b, int i) { + (v.getAUse() = use or v.hasRefinement(use, _)) and + exists(int j | + b.getNode(j) = use and + i = 2*j + ) + } + + /** + * A definition of an SSA variable occurring at the specified position. + * This is either a phi node, a `VariableUpdate`, or a parameter. + */ + cached predicate ssaDef(SsaSourceVariable v, BasicBlock b) { + EssaDefinitions::phiNode(v, b) + or + EssaDefinitions::variableUpdate(v, _, b, _, _) + or + EssaDefinitions::piNode(v, _, b) + } + + /* + * The construction of SSA form ensures that each use of a variable is + * dominated by its definition. A definition of an SSA variable therefore + * reaches a `ControlFlowNode` if it is the _closest_ SSA variable definition + * that dominates the node. If two definitions dominate a node then one must + * dominate the other, so therefore the definition of _closest_ is given by the + * dominator tree. Thus, reaching definitions can be calculated in terms of + * dominance. + */ + + /** The maximum rank index for the given variable and basic block. */ + cached int lastRank(SsaSourceVariable v, BasicBlock b) { + result = max(int rankix | defUseRank(v, b, rankix, _)) + or + not defUseRank(v, b, _, _) and (EssaDefinitions::phiNode(v, b) or EssaDefinitions::piNode(v, _, b)) and result = 0 + } + + private predicate ssaDefRank(SsaSourceVariable v, BasicBlock b, int rankix, int i) { + EssaDefinitions::variableUpdate(v, _, b, rankix, i) + or + EssaDefinitions::phiNode(v, b) and rankix = 0 and i = phiIndex() + or + EssaDefinitions::piNode(v, _, b) and EssaDefinitions::phiNode(v, b) and rankix = -1 and i = piIndex() + or + EssaDefinitions::piNode(v, _, b) and not EssaDefinitions::phiNode(v, b) and rankix = 0 and i = piIndex() + } + + /** The SSA definition reaches the rank index `rankix` in its own basic block `b`. */ + cached predicate ssaDefReachesRank(SsaSourceVariable v, BasicBlock b, int i, int rankix) { + ssaDefRank(v, b, rankix, i) or + ssaDefReachesRank(v, b, i, rankix-1) and rankix <= lastRank(v, b) and not ssaDefRank(v, b, rankix, _) + } + + /** + * The SSA definition of `v` at `def` reaches `use` in the same basic block + * without crossing another SSA definition of `v`. + */ + cached predicate ssaDefReachesUseWithinBlock(SsaSourceVariable v, BasicBlock b, int i, ControlFlowNode use) { + exists(int rankix, int useix | + ssaDefReachesRank(v, b, i, rankix) and + defUseRank(v, b, rankix, useix) and + variableUse(v, use, b, useix) + ) + } + + cached module LivenessImpl { + + cached predicate liveAtExit(SsaSourceVariable v, BasicBlock b) { + liveAtEntry(v, b.getASuccessor()) + } + + cached predicate liveAtEntry(SsaSourceVariable v, BasicBlock b) { + SsaComputeImpl::defUseRank(v, b, 1, _) and not SsaComputeImpl::defRank(v, b, 1, _) + or + not SsaComputeImpl::defUseRank(v, b, _, _) and liveAtExit(v, b) + } + + } + + cached module SsaDefinitionsImpl { + + /** + * The SSA definition of `v` at `def` reaches the end of a basic block `b`, at + * which point it is still live, without crossing another SSA definition of `v`. + */ + cached + predicate reachesEndOfBlock(SsaSourceVariable v, BasicBlock defbb, int defindex, BasicBlock b) { + Liveness::liveAtExit(v, b) and + ( + defbb = b and SsaComputeImpl::ssaDefReachesRank(v, defbb, defindex, SsaComputeImpl::lastRank(v, b)) + or + exists(BasicBlock idom | + idom = b.getImmediateDominator() and + // It is sufficient to traverse the dominator graph, cf. discussion above. + reachesEndOfBlock(v, defbb, defindex, idom) and + not SsaComputeImpl::ssaDef(v, b) + ) + ) + } + + /** + * The SSA definition of `v` at `(defbb, defindex)` reaches `use` without crossing another + * SSA definition of `v`. + */ + cached + predicate reachesUse(SsaSourceVariable v, BasicBlock defbb, int defindex, ControlFlowNode use) { + SsaComputeImpl::ssaDefReachesUseWithinBlock(v, defbb, defindex, use) or + exists(BasicBlock b | + SsaComputeImpl::variableUse(v, use, b, _) and + reachesEndOfBlock(v, defbb, defindex, b.getAPredecessor()) and + not SsaComputeImpl::ssaDefReachesUseWithinBlock(v, b, _, use) + ) + } + + /*** + * Holds if `(defbb, defindex)` is an SSA definition of `v` that reaches an exit without crossing another + * SSA definition of `v`. + */ + cached + predicate reachesExit(SsaSourceVariable v, BasicBlock defbb, int defindex) { + exists(BasicBlock last, ControlFlowNode use, int index | + not Liveness::liveAtExit(v, last) and + reachesUse(v, defbb, defindex, use) and + SsaComputeImpl::defUseRank(v, last, SsaComputeImpl::lastRank(v, last), index) and + SsaComputeImpl::variableUse(v, use, last, index) + ) + } + + } + +} + +import SsaComputeImpl::SsaDefinitionsImpl as SsaDefinitions +import SsaComputeImpl::EssaDefinitionsImpl as EssaDefinitions +import SsaComputeImpl::LivenessImpl as Liveness + +/* This is exported primarily for testing */ + + +/* A note on numbering + * In order to create an SSA graph, we need an order of definitions and uses within a basic block. + * To do this we index definitions and uses as follows: + * Phi-functions have an index of -1, so precede all normal uses and definitions in a block. + * Pi-functions (on edges) have an index of -2 in the successor block, so precede all other uses and definitions, including phi-functions + * A use of a variable at at a CFG node is assumed to occur before any definition at the same node, so: + * * a use at the `j`th node of a block is given the index `2*j` and + * * a definition at the `j`th node of a block is given the index `2*j + 1`. + */ + +pragma [inline] +int phiIndex() { result = -1 } + +pragma [inline] +int piIndex() { result = -2 } + + diff --git a/python/ql/src/semmle/files/FileSystem.qll b/python/ql/src/semmle/files/FileSystem.qll new file mode 100644 index 000000000000..4ec67c7c2e6b --- /dev/null +++ b/python/ql/src/semmle/files/FileSystem.qll @@ -0,0 +1,2 @@ +/** Provides classes for working with files and folders. */ +import semmle.python.Files diff --git a/python/ql/src/semmle/python/AST.qll b/python/ql/src/semmle/python/AST.qll new file mode 100644 index 000000000000..0ac8db03e32f --- /dev/null +++ b/python/ql/src/semmle/python/AST.qll @@ -0,0 +1,57 @@ +import python + +/** Syntactic node (Class, Function, Module, Expr, Stmt or Comprehension) corresponding to a flow node */ +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` */ + ControlFlowNode getAFlowNode() { + py_flow_bb_node(result, this, _, _) + } + + /** Gets the location for this AST node */ + Location getLocation() { + none() + } + + /** Whether this syntactic element is artificial, that is it is generated + * by the compiler and is not present in the source */ + predicate isArtificial() { + none() + } + + /** Gets a child node of this node in the AST. This predicate exists to aid exploration of the AST + * and other experiments. The child-parent relation may not be meaningful. + * For a more meaningful relation in terms of dependency use + * Expr.getASubExpression(), Stmt.getASubStatement(), Stmt.getASubExpression() or + * Scope.getAStmt(). + */ + abstract AstNode getAChildNode(); + + /** Gets the parent node of this node in the AST. This predicate exists to aid exploration of the AST + * and other experiments. The child-parent relation may not be meaningful. + * For a more meaningful relation in terms of dependency use + * Expr.getASubExpression(), Stmt.getASubStatement(), Stmt.getASubExpression() or + * Scope.getAStmt() applied to the parent. + */ + AstNode getParentNode() { + result.getAChildNode() = this + } + + /** Whether this contains `inner` syntactically */ + predicate contains(AstNode inner) { + this.getAChildNode+() = inner + } + + /** Whether this contains `inner` syntactically and `inner` has the same scope as `this` */ + predicate containsInScope(AstNode inner) { + this.contains(inner) and + this.getScope() = inner.getScope() and + not inner instanceof Scope + } + +} diff --git a/python/ql/src/semmle/python/Assigns.qll b/python/ql/src/semmle/python/Assigns.qll new file mode 100644 index 000000000000..ad72645ffd5a --- /dev/null +++ b/python/ql/src/semmle/python/Assigns.qll @@ -0,0 +1,19 @@ +/** + * In order to handle data flow and other analyses efficiently the extractor transforms various statements which perform binding in assignments. + * These classes provide a wrapper to provide a more 'natural' interface to the syntactic elements transformed to assignments. + */ + +import python + + +/** An assignment statement */ +class AssignStmt extends Assign { + + AssignStmt() { + not this instanceof FunctionDef and not this instanceof ClassDef + } + + override string toString() { + result = "AssignStmt" + } +} diff --git a/python/ql/src/semmle/python/AstExtended.qll b/python/ql/src/semmle/python/AstExtended.qll new file mode 100644 index 000000000000..b109fda18e29 --- /dev/null +++ b/python/ql/src/semmle/python/AstExtended.qll @@ -0,0 +1,118 @@ +import python + +/* Parents */ + +/** Internal implementation class */ +library class FunctionParent extends FunctionParent_ { + +} + +/** Internal implementation class */ +library class ArgumentsParent extends ArgumentsParent_ { + +} + +/** Internal implementation class */ +library class ExprListParent extends ExprListParent_ { + +} + +/** Internal implementation class */ +library class ExprContextParent extends ExprContextParent_ { + +} + +/** Internal implementation class */ +library class StmtListParent extends StmtListParent_ { + +} + +/** Internal implementation class */ +library class StrListParent extends StrListParent_ { + +} + +/** Internal implementation class */ +library class ExprParent extends ExprParent_ { + +} + +library class DictItem extends DictItem_, AstNode { + + override string toString() { + result = DictItem_.super.toString() + } + + override AstNode getAChildNode() { none() } + + override Scope getScope() { none() } + +} + +/** A comprehension part, the 'for a in seq' part of [ a * a for a in seq ] */ +class Comprehension extends Comprehension_, AstNode { + + /** Gets the scope of this comprehension */ + override Scope getScope() { + /* Comprehensions exists only in Python 2 list comprehensions, so their scope is that of the list comp. */ + exists(ListComp l | + this = l.getAGenerator() | + result = l.getScope() + ) + } + + override string toString() { + result = "Comprehension" + } + + override Location getLocation() { + result = Comprehension_.super.getLocation() + } + + override AstNode getAChildNode() { + result = this.getASubExpression() + } + + Expr getASubExpression() { + result = this.getIter() or + result = this.getAnIf() or + result = this.getTarget() + } + +} + +class BytesOrStr extends BytesOrStr_ { + +} + +/** Part of a string literal formed by implicit concatenation. + * For example the string literal "abc" expressed in the source as `"a" "b" "c"` + * would be composed of three `StringPart`s. + * + */ +class StringPart extends StringPart_, AstNode { + + override Scope getScope() { + exists(Bytes b | this = b.getAnImplicitlyConcatenatedPart() | result = b.getScope()) + or + exists(Unicode u | this = u.getAnImplicitlyConcatenatedPart() | result = u.getScope()) + } + + override AstNode getAChildNode() { + none() + } + + override string toString() { + result = StringPart_.super.toString() + } + + override Location getLocation() { + result = StringPart_.super.getLocation() + } + +} + +class StringPartList extends StringPartList_ { + +} + diff --git a/python/ql/src/semmle/python/AstGenerated.qll b/python/ql/src/semmle/python/AstGenerated.qll new file mode 100644 index 000000000000..d75744398f08 --- /dev/null +++ b/python/ql/src/semmle/python/AstGenerated.qll @@ -0,0 +1,2791 @@ +import python + +library class Add_ extends @py_Add, Operator { + + override string toString() { + result = "Add" + } + +} + +library class And_ extends @py_And, Boolop { + + override string toString() { + result = "And" + } + +} + +library class AnnAssign_ extends @py_AnnAssign, Stmt { + + + /** Gets the value of this annotated assignment. */ + Expr getValue() { + py_exprs(result, _, this, 1) + } + + + /** Gets the annotation of this annotated assignment. */ + Expr getAnnotation() { + py_exprs(result, _, this, 2) + } + + + /** Gets the target of this annotated assignment. */ + Expr getTarget() { + py_exprs(result, _, this, 3) + } + + override string toString() { + result = "AnnAssign" + } + +} + +library class Assert_ extends @py_Assert, Stmt { + + + /** Gets the value being tested of this assert statement. */ + Expr getTest() { + py_exprs(result, _, this, 1) + } + + + /** Gets the failure message of this assert statement. */ + Expr getMsg() { + py_exprs(result, _, this, 2) + } + + override string toString() { + result = "Assert" + } + +} + +library class Assign_ extends @py_Assign, Stmt { + + + /** Gets the value of this assignment statement. */ + Expr getValue() { + py_exprs(result, _, this, 1) + } + + + /** Gets the targets of this assignment statement. */ + ExprList getTargets() { + py_expr_lists(result, this, 2) + } + + + /** Gets the nth target of this assignment statement. */ + Expr getTarget(int index) { + result = this.getTargets().getItem(index) + } + + /** Gets a target of this assignment statement. */ + Expr getATarget() { + result = this.getTargets().getAnItem() + } + + override string toString() { + result = "Assign" + } + +} + +library class Attribute_ extends @py_Attribute, Expr { + + + /** Gets the object of this attribute expression. */ + Expr getValue() { + py_exprs(result, _, this, 2) + } + + + /** Gets the attribute name of this attribute expression. */ + string getAttr() { + py_strs(result, this, 3) + } + + + /** Gets the context of this attribute expression. */ + ExprContext getCtx() { + py_expr_contexts(result, _, this) + } + + override string toString() { + result = "Attribute" + } + +} + +library class AugAssign_ extends @py_AugAssign, Stmt { + + + /** Gets the operation of this augmented assignment statement. */ + BinaryExpr getOperation() { + py_exprs(result, _, this, 1) + } + + override string toString() { + result = "AugAssign" + } + +} + +library class AugLoad_ extends @py_AugLoad, ExprContext { + + override string toString() { + result = "AugLoad" + } + +} + +library class AugStore_ extends @py_AugStore, ExprContext { + + override string toString() { + result = "AugStore" + } + +} + +library class Await_ extends @py_Await, Expr { + + + /** Gets the expression waited upon of this await expression. */ + Expr getValue() { + py_exprs(result, _, this, 2) + } + + override string toString() { + result = "Await" + } + +} + +library class BinaryExpr_ extends @py_BinaryExpr, Expr { + + + /** Gets the left sub-expression of this binary expression. */ + Expr getLeft() { + py_exprs(result, _, this, 2) + } + + + /** Gets the operator of this binary expression. */ + Operator getOp() { + py_operators(result, _, this) + } + + + /** Gets the right sub-expression of this binary expression. */ + Expr getRight() { + py_exprs(result, _, this, 4) + } + + override ExprParent getParent() { + py_exprs(this, _, result, _) + } + + override string toString() { + result = "BinaryExpr" + } + +} + +library class BitAnd_ extends @py_BitAnd, Operator { + + override string toString() { + result = "BitAnd" + } + +} + +library class BitOr_ extends @py_BitOr, Operator { + + override string toString() { + result = "BitOr" + } + +} + +library class BitXor_ extends @py_BitXor, Operator { + + override string toString() { + result = "BitXor" + } + +} + +library class BoolExpr_ extends @py_BoolExpr, Expr { + + + /** Gets the operator of this boolean expression. */ + Boolop getOp() { + py_boolops(result, _, this) + } + + + /** Gets the sub-expressions of this boolean expression. */ + ExprList getValues() { + py_expr_lists(result, this, 3) + } + + + /** Gets the nth sub-expression of this boolean expression. */ + Expr getValue(int index) { + result = this.getValues().getItem(index) + } + + /** Gets a sub-expression of this boolean expression. */ + Expr getAValue() { + result = this.getValues().getAnItem() + } + + override string toString() { + result = "BoolExpr" + } + +} + +library class Break_ extends @py_Break, Stmt { + + override string toString() { + result = "Break" + } + +} + +library class Bytes_ extends @py_Bytes, Expr { + + + /** Gets the value of this bytes expression. */ + string getS() { + py_bytes(result, this, 2) + } + + + /** Gets the prefix of this bytes expression. */ + string getPrefix() { + py_bytes(result, this, 3) + } + + + /** Gets the implicitly_concatenated_parts of this bytes expression. */ + StringPartList getImplicitlyConcatenatedParts() { + py_StringPart_lists(result, this) + } + + + /** Gets the nth implicitly_concatenated_part of this bytes expression. */ + StringPart getImplicitlyConcatenatedPart(int index) { + result = this.getImplicitlyConcatenatedParts().getItem(index) + } + + /** Gets an implicitly_concatenated_part of this bytes expression. */ + StringPart getAnImplicitlyConcatenatedPart() { + result = this.getImplicitlyConcatenatedParts().getAnItem() + } + + override string toString() { + result = "Bytes" + } + +} + +library class BytesOrStr_ extends @py_Bytes_or_Str { + + string toString() { + result = "BytesOrStr" + } + +} + +library class Call_ extends @py_Call, Expr { + + + /** Gets the callable of this call expression. */ + Expr getFunc() { + py_exprs(result, _, this, 2) + } + + + /** Gets the positional arguments of this call expression. */ + ExprList getPositionalArgs() { + py_expr_lists(result, this, 3) + } + + + /** Gets the nth positional argument of this call expression. */ + Expr getPositionalArg(int index) { + result = this.getPositionalArgs().getItem(index) + } + + /** Gets a positional argument of this call expression. */ + Expr getAPositionalArg() { + result = this.getPositionalArgs().getAnItem() + } + + + /** Gets the named arguments of this call expression. */ + DictItemList getNamedArgs() { + py_dict_item_lists(result, this) + } + + + /** Gets the nth named argument of this call expression. */ + DictItem getNamedArg(int index) { + result = this.getNamedArgs().getItem(index) + } + + /** Gets a named argument of this call expression. */ + DictItem getANamedArg() { + result = this.getNamedArgs().getAnItem() + } + + override string toString() { + result = "Call" + } + +} + +library class Class_ extends @py_Class { + + + /** Gets the name of this class. */ + string getName() { + py_strs(result, this, 0) + } + + + /** Gets the body of this class. */ + StmtList getBody() { + py_stmt_lists(result, this, 1) + } + + + /** Gets the nth statement of this class. */ + Stmt getStmt(int index) { + result = this.getBody().getItem(index) + } + + /** Gets a statement of this class. */ + Stmt getAStmt() { + result = this.getBody().getAnItem() + } + + ClassExpr getParent() { + py_Classes(this, result) + } + + string toString() { + result = "Class" + } + +} + +library class ClassExpr_ extends @py_ClassExpr, Expr { + + + /** Gets the name of this class definition. */ + string getName() { + py_strs(result, this, 2) + } + + + /** Gets the bases of this class definition. */ + ExprList getBases() { + py_expr_lists(result, this, 3) + } + + + /** Gets the nth base of this class definition. */ + Expr getBase(int index) { + result = this.getBases().getItem(index) + } + + /** Gets a base of this class definition. */ + Expr getABase() { + result = this.getBases().getAnItem() + } + + + /** Gets the keyword arguments of this class definition. */ + DictItemList getKeywords() { + py_dict_item_lists(result, this) + } + + + /** Gets the nth keyword argument of this class definition. */ + DictItem getKeyword(int index) { + result = this.getKeywords().getItem(index) + } + + /** Gets a keyword argument of this class definition. */ + DictItem getAKeyword() { + result = this.getKeywords().getAnItem() + } + + + /** Gets the class scope of this class definition. */ + Class getInnerScope() { + py_Classes(result, this) + } + + override string toString() { + result = "ClassExpr" + } + +} + +library class Compare_ extends @py_Compare, Expr { + + + /** Gets the left sub-expression of this compare expression. */ + Expr getLeft() { + py_exprs(result, _, this, 2) + } + + + /** Gets the comparison operators of this compare expression. */ + CmpopList getOps() { + py_cmpop_lists(result, this) + } + + + /** Gets the nth comparison operator of this compare expression. */ + Cmpop getOp(int index) { + result = this.getOps().getItem(index) + } + + /** Gets a comparison operator of this compare expression. */ + Cmpop getAnOp() { + result = this.getOps().getAnItem() + } + + + /** Gets the right sub-expressions of this compare expression. */ + ExprList getComparators() { + py_expr_lists(result, this, 4) + } + + + /** Gets the nth right sub-expression of this compare expression. */ + Expr getComparator(int index) { + result = this.getComparators().getItem(index) + } + + /** Gets a right sub-expression of this compare expression. */ + Expr getAComparator() { + result = this.getComparators().getAnItem() + } + + override string toString() { + result = "Compare" + } + +} + +library class Continue_ extends @py_Continue, Stmt { + + override string toString() { + result = "Continue" + } + +} + +library class Del_ extends @py_Del, ExprContext { + + override string toString() { + result = "Del" + } + +} + +library class Delete_ extends @py_Delete, Stmt { + + + /** Gets the targets of this delete statement. */ + ExprList getTargets() { + py_expr_lists(result, this, 1) + } + + + /** Gets the nth target of this delete statement. */ + Expr getTarget(int index) { + result = this.getTargets().getItem(index) + } + + /** Gets a target of this delete statement. */ + Expr getATarget() { + result = this.getTargets().getAnItem() + } + + override string toString() { + result = "Delete" + } + +} + +library class Dict_ extends @py_Dict, Expr { + + + /** Gets the items of this dictionary expression. */ + DictItemList getItems() { + py_dict_item_lists(result, this) + } + + + /** Gets the nth item of this dictionary expression. */ + DictItem getItem(int index) { + result = this.getItems().getItem(index) + } + + /** Gets an item of this dictionary expression. */ + DictItem getAnItem() { + result = this.getItems().getAnItem() + } + + override string toString() { + result = "Dict" + } + +} + +library class DictComp_ extends @py_DictComp, Expr { + + + /** Gets the implementation of this dictionary comprehension. */ + Function getFunction() { + py_Functions(result, this) + } + + + /** Gets the iterable of this dictionary comprehension. */ + Expr getIterable() { + py_exprs(result, _, this, 3) + } + + override string toString() { + result = "DictComp" + } + +} + +library class DictUnpacking_ extends @py_DictUnpacking, DictItem { + + + /** Gets the location of this dictionary unpacking. */ + override Location getLocation() { + py_locations(result, this) + } + + + /** Gets the value of this dictionary unpacking. */ + Expr getValue() { + py_exprs(result, _, this, 1) + } + + override string toString() { + result = "DictUnpacking" + } + +} + +library class Div_ extends @py_Div, Operator { + + override string toString() { + result = "Div" + } + +} + +library class Ellipsis_ extends @py_Ellipsis, Expr { + + override string toString() { + result = "Ellipsis" + } + +} + +library class Eq_ extends @py_Eq, Cmpop { + + override string toString() { + result = "Eq" + } + +} + +library class ExceptStmt_ extends @py_ExceptStmt, Stmt { + + + /** Gets the type of this except block. */ + Expr getType() { + py_exprs(result, _, this, 1) + } + + + /** Gets the name of this except block. */ + Expr getName() { + py_exprs(result, _, this, 2) + } + + + /** Gets the body of this except block. */ + StmtList getBody() { + py_stmt_lists(result, this, 3) + } + + + /** Gets the nth statement of this except block. */ + Stmt getStmt(int index) { + result = this.getBody().getItem(index) + } + + /** Gets a statement of this except block. */ + Stmt getAStmt() { + result = this.getBody().getAnItem() + } + + override string toString() { + result = "ExceptStmt" + } + +} + +library class Exec_ extends @py_Exec, Stmt { + + + /** Gets the body of this exec statement. */ + Expr getBody() { + py_exprs(result, _, this, 1) + } + + + /** Gets the globals of this exec statement. */ + Expr getGlobals() { + py_exprs(result, _, this, 2) + } + + + /** Gets the locals of this exec statement. */ + Expr getLocals() { + py_exprs(result, _, this, 3) + } + + override string toString() { + result = "Exec" + } + +} + +library class ExprStmt_ extends @py_Expr_stmt, Stmt { + + + /** Gets the value of this expr statement. */ + Expr getValue() { + py_exprs(result, _, this, 1) + } + + override string toString() { + result = "ExprStmt" + } + +} + +library class Filter_ extends @py_Filter, Expr { + + + /** Gets the filtered value of this template filter expression. */ + Expr getValue() { + py_exprs(result, _, this, 2) + } + + + /** Gets the filter of this template filter expression. */ + Expr getFilter() { + py_exprs(result, _, this, 3) + } + + override string toString() { + result = "Filter" + } + +} + +library class FloorDiv_ extends @py_FloorDiv, Operator { + + override string toString() { + result = "FloorDiv" + } + +} + +library class For_ extends @py_For, Stmt { + + + /** Gets the target of this for statement. */ + Expr getTarget() { + py_exprs(result, _, this, 1) + } + + + /** Gets the iterable of this for statement. */ + Expr getIter() { + py_exprs(result, _, this, 2) + } + + + /** Gets the body of this for statement. */ + StmtList getBody() { + py_stmt_lists(result, this, 3) + } + + + /** Gets the nth statement of this for statement. */ + Stmt getStmt(int index) { + result = this.getBody().getItem(index) + } + + /** Gets a statement of this for statement. */ + Stmt getAStmt() { + result = this.getBody().getAnItem() + } + + + /** Gets the else block of this for statement. */ + StmtList getOrelse() { + py_stmt_lists(result, this, 4) + } + + + /** Gets the nth else statement of this for statement. */ + Stmt getOrelse(int index) { + result = this.getOrelse().getItem(index) + } + + /** Gets an else statement of this for statement. */ + Stmt getAnOrelse() { + result = this.getOrelse().getAnItem() + } + + + /** Whether the async property of this for statement is true. */ + predicate isAsync() { + py_bools(this, 5) + } + + override string toString() { + result = "For" + } + +} + +library class FormattedValue_ extends @py_FormattedValue, Expr { + + + /** Gets the expression to be formatted of this formatted value. */ + Expr getValue() { + py_exprs(result, _, this, 2) + } + + + /** Gets the type conversion of this formatted value. */ + string getConversion() { + py_strs(result, this, 3) + } + + + /** Gets the format specifier of this formatted value. */ + Fstring getFormatSpec() { + py_exprs(result, _, this, 4) + } + + override string toString() { + result = "FormattedValue" + } + +} + +library class Function_ extends @py_Function { + + + /** Gets the name of this function. */ + string getName() { + py_strs(result, this, 0) + } + + + /** Gets the positional parameter list of this function. */ + ParameterList getArgs() { + py_parameter_lists(result, this) + } + + + /** Gets the nth positional parameter of this function. */ + Parameter getArg(int index) { + result = this.getArgs().getItem(index) + } + + /** Gets a positional parameter of this function. */ + Parameter getAnArg() { + result = this.getArgs().getAnItem() + } + + + /** Gets the tuple (*) parameter of this function. */ + Expr getVararg() { + py_exprs(result, _, this, 2) + } + + + /** Gets the keyword-only parameter list of this function. */ + ExprList getKwonlyargs() { + py_expr_lists(result, this, 3) + } + + + /** Gets the nth keyword-only parameter of this function. */ + Expr getKwonlyarg(int index) { + result = this.getKwonlyargs().getItem(index) + } + + /** Gets a keyword-only parameter of this function. */ + Expr getAKwonlyarg() { + result = this.getKwonlyargs().getAnItem() + } + + + /** Gets the dictionary (**) parameter of this function. */ + Expr getKwarg() { + py_exprs(result, _, this, 4) + } + + + /** Gets the body of this function. */ + StmtList getBody() { + py_stmt_lists(result, this, 5) + } + + + /** Gets the nth statement of this function. */ + Stmt getStmt(int index) { + result = this.getBody().getItem(index) + } + + /** Gets a statement of this function. */ + Stmt getAStmt() { + result = this.getBody().getAnItem() + } + + + /** Whether the async property of this function is true. */ + predicate isAsync() { + py_bools(this, 6) + } + + FunctionParent getParent() { + py_Functions(this, result) + } + + string toString() { + result = "Function" + } + +} + +library class FunctionExpr_ extends @py_FunctionExpr, Expr { + + + /** Gets the name of this function definition. */ + string getName() { + py_strs(result, this, 2) + } + + + /** Gets the parameters of this function definition. */ + Arguments getArgs() { + py_arguments(result, this) + } + + + /** Gets the return annotation of this function definition. */ + Expr getReturns() { + py_exprs(result, _, this, 4) + } + + + /** Gets the function scope of this function definition. */ + Function getInnerScope() { + py_Functions(result, this) + } + + override string toString() { + result = "FunctionExpr" + } + +} + +library class FunctionParent_ extends @py_Function_parent { + + string toString() { + result = "FunctionParent" + } + +} + +library class GeneratorExp_ extends @py_GeneratorExp, Expr { + + + /** Gets the implementation of this generator expression. */ + Function getFunction() { + py_Functions(result, this) + } + + + /** Gets the iterable of this generator expression. */ + Expr getIterable() { + py_exprs(result, _, this, 3) + } + + override string toString() { + result = "GeneratorExp" + } + +} + +library class Global_ extends @py_Global, Stmt { + + + /** Gets the names of this global statement. */ + StringList getNames() { + py_str_lists(result, this) + } + + + /** Gets the nth name of this global statement. */ + string getName(int index) { + result = this.getNames().getItem(index) + } + + /** Gets a name of this global statement. */ + string getAName() { + result = this.getNames().getAnItem() + } + + override string toString() { + result = "Global" + } + +} + +library class Gt_ extends @py_Gt, Cmpop { + + override string toString() { + result = "Gt" + } + +} + +library class GtE_ extends @py_GtE, Cmpop { + + override string toString() { + result = "GtE" + } + +} + +library class If_ extends @py_If, Stmt { + + + /** Gets the test of this if statement. */ + Expr getTest() { + py_exprs(result, _, this, 1) + } + + + /** Gets the if-true block of this if statement. */ + StmtList getBody() { + py_stmt_lists(result, this, 2) + } + + + /** Gets the nth if-true statement of this if statement. */ + Stmt getStmt(int index) { + result = this.getBody().getItem(index) + } + + /** Gets an if-true statement of this if statement. */ + Stmt getAStmt() { + result = this.getBody().getAnItem() + } + + + /** Gets the if-false block of this if statement. */ + StmtList getOrelse() { + py_stmt_lists(result, this, 3) + } + + + /** Gets the nth if-false statement of this if statement. */ + Stmt getOrelse(int index) { + result = this.getOrelse().getItem(index) + } + + /** Gets an if-false statement of this if statement. */ + Stmt getAnOrelse() { + result = this.getOrelse().getAnItem() + } + + override string toString() { + result = "If" + } + +} + +library class IfExp_ extends @py_IfExp, Expr { + + + /** Gets the test of this if expression. */ + Expr getTest() { + py_exprs(result, _, this, 2) + } + + + /** Gets the if-true expression of this if expression. */ + Expr getBody() { + py_exprs(result, _, this, 3) + } + + + /** Gets the if-false expression of this if expression. */ + Expr getOrelse() { + py_exprs(result, _, this, 4) + } + + override string toString() { + result = "IfExp" + } + +} + +library class Import_ extends @py_Import, Stmt { + + + /** Gets the alias list of this import statement. */ + AliasList getNames() { + py_alias_lists(result, this) + } + + + /** Gets the nth alias of this import statement. */ + Alias getName(int index) { + result = this.getNames().getItem(index) + } + + /** Gets an alias of this import statement. */ + Alias getAName() { + result = this.getNames().getAnItem() + } + + override string toString() { + result = "Import" + } + +} + +library class ImportExpr_ extends @py_ImportExpr, Expr { + + + /** Gets the level of this import expression. */ + int getLevel() { + py_ints(result, this) + } + + + /** Gets the name of this import expression. */ + string getName() { + py_strs(result, this, 3) + } + + + /** Whether the top level property of this import expression is true. */ + predicate isTop() { + py_bools(this, 4) + } + + override string toString() { + result = "ImportExpr" + } + +} + +library class ImportStar_ extends @py_ImportStar, Stmt { + + + /** Gets the module of this import * statement. */ + Expr getModule() { + py_exprs(result, _, this, 1) + } + + override string toString() { + result = "ImportStar" + } + +} + +library class ImportMember_ extends @py_ImportMember, Expr { + + + /** Gets the module of this from import. */ + Expr getModule() { + py_exprs(result, _, this, 2) + } + + + /** Gets the name of this from import. */ + string getName() { + py_strs(result, this, 3) + } + + override string toString() { + result = "ImportMember" + } + +} + +library class In_ extends @py_In, Cmpop { + + override string toString() { + result = "In" + } + +} + +library class Invert_ extends @py_Invert, Unaryop { + + override string toString() { + result = "Invert" + } + +} + +library class Is_ extends @py_Is, Cmpop { + + override string toString() { + result = "Is" + } + +} + +library class IsNot_ extends @py_IsNot, Cmpop { + + override string toString() { + result = "IsNot" + } + +} + +library class Fstring_ extends @py_Fstring, Expr { + + + /** Gets the values of this formatted string literal. */ + ExprList getValues() { + py_expr_lists(result, this, 2) + } + + + /** Gets the nth value of this formatted string literal. */ + Expr getValue(int index) { + result = this.getValues().getItem(index) + } + + /** Gets a value of this formatted string literal. */ + Expr getAValue() { + result = this.getValues().getAnItem() + } + + override ExprParent getParent() { + py_exprs(this, _, result, _) + } + + override string toString() { + result = "Fstring" + } + +} + +library class KeyValuePair_ extends @py_KeyValuePair, DictItem { + + + /** Gets the location of this key-value pair. */ + override Location getLocation() { + py_locations(result, this) + } + + + /** Gets the value of this key-value pair. */ + Expr getValue() { + py_exprs(result, _, this, 1) + } + + + /** Gets the key of this key-value pair. */ + Expr getKey() { + py_exprs(result, _, this, 2) + } + + override string toString() { + result = "KeyValuePair" + } + +} + +library class LShift_ extends @py_LShift, Operator { + + override string toString() { + result = "LShift" + } + +} + +library class Lambda_ extends @py_Lambda, Expr { + + + /** Gets the arguments of this lambda expression. */ + Arguments getArgs() { + py_arguments(result, this) + } + + + /** Gets the function scope of this lambda expression. */ + Function getInnerScope() { + py_Functions(result, this) + } + + override string toString() { + result = "Lambda" + } + +} + +library class List_ extends @py_List, Expr { + + + /** Gets the element list of this list expression. */ + ExprList getElts() { + py_expr_lists(result, this, 2) + } + + + /** Gets the nth element of this list expression. */ + Expr getElt(int index) { + result = this.getElts().getItem(index) + } + + /** Gets an element of this list expression. */ + Expr getAnElt() { + result = this.getElts().getAnItem() + } + + + /** Gets the context of this list expression. */ + ExprContext getCtx() { + py_expr_contexts(result, _, this) + } + + override string toString() { + result = "List" + } + +} + +library class ListComp_ extends @py_ListComp, Expr { + + + /** Gets the implementation of this list comprehension. */ + Function getFunction() { + py_Functions(result, this) + } + + + /** Gets the iterable of this list comprehension. */ + Expr getIterable() { + py_exprs(result, _, this, 3) + } + + + /** Gets the generators of this list comprehension. */ + ComprehensionList getGenerators() { + py_comprehension_lists(result, this) + } + + + /** Gets the nth generator of this list comprehension. */ + Comprehension getGenerator(int index) { + result = this.getGenerators().getItem(index) + } + + /** Gets a generator of this list comprehension. */ + Comprehension getAGenerator() { + result = this.getGenerators().getAnItem() + } + + + /** Gets the elements of this list comprehension. */ + Expr getElt() { + py_exprs(result, _, this, 5) + } + + override string toString() { + result = "ListComp" + } + +} + +library class Load_ extends @py_Load, ExprContext { + + override string toString() { + result = "Load" + } + +} + +library class Lt_ extends @py_Lt, Cmpop { + + override string toString() { + result = "Lt" + } + +} + +library class LtE_ extends @py_LtE, Cmpop { + + override string toString() { + result = "LtE" + } + +} + +library class MatMult_ extends @py_MatMult, Operator { + + override string toString() { + result = "MatMult" + } + +} + +library class Mod_ extends @py_Mod, Operator { + + override string toString() { + result = "Mod" + } + +} + +library class Module_ extends @py_Module { + + + /** Gets the name of this module. */ + string getName() { + py_strs(result, this, 0) + } + + + /** Gets the hash (not populated) of this module. */ + string getHash() { + py_strs(result, this, 1) + } + + + /** Gets the body of this module. */ + StmtList getBody() { + py_stmt_lists(result, this, 2) + } + + + /** Gets the nth statement of this module. */ + Stmt getStmt(int index) { + result = this.getBody().getItem(index) + } + + /** Gets a statement of this module. */ + Stmt getAStmt() { + result = this.getBody().getAnItem() + } + + + /** Gets the kind of this module. */ + string getKind() { + py_strs(result, this, 3) + } + + string toString() { + result = "Module" + } + +} + +library class Mult_ extends @py_Mult, Operator { + + override string toString() { + result = "Mult" + } + +} + +library class Name_ extends @py_Name, Expr { + + + /** Gets the variable of this name expression. */ + Variable getVariable() { + py_variables(result, this) + } + + + /** Gets the context of this name expression. */ + ExprContext getCtx() { + py_expr_contexts(result, _, this) + } + + override ExprParent getParent() { + py_exprs(this, _, result, _) + } + + override string toString() { + result = "Name" + } + +} + +library class Nonlocal_ extends @py_Nonlocal, Stmt { + + + /** Gets the names of this nonlocal statement. */ + StringList getNames() { + py_str_lists(result, this) + } + + + /** Gets the nth name of this nonlocal statement. */ + string getName(int index) { + result = this.getNames().getItem(index) + } + + /** Gets a name of this nonlocal statement. */ + string getAName() { + result = this.getNames().getAnItem() + } + + override string toString() { + result = "Nonlocal" + } + +} + +library class Not_ extends @py_Not, Unaryop { + + override string toString() { + result = "Not" + } + +} + +library class NotEq_ extends @py_NotEq, Cmpop { + + override string toString() { + result = "NotEq" + } + +} + +library class NotIn_ extends @py_NotIn, Cmpop { + + override string toString() { + result = "NotIn" + } + +} + +library class Num_ extends @py_Num, Expr { + + + /** Gets the value of this numeric literal. */ + string getN() { + py_numbers(result, this, 2) + } + + + /** Gets the text of this numeric literal. */ + string getText() { + py_numbers(result, this, 3) + } + + override string toString() { + result = "Num" + } + +} + +library class Or_ extends @py_Or, Boolop { + + override string toString() { + result = "Or" + } + +} + +library class Param_ extends @py_Param, ExprContext { + + override string toString() { + result = "Param" + } + +} + +library class Pass_ extends @py_Pass, Stmt { + + override string toString() { + result = "Pass" + } + +} + +library class PlaceHolder_ extends @py_PlaceHolder, Expr { + + + /** Gets the variable of this template place-holder expression. */ + Variable getVariable() { + py_variables(result, this) + } + + + /** Gets the context of this template place-holder expression. */ + ExprContext getCtx() { + py_expr_contexts(result, _, this) + } + + override string toString() { + result = "PlaceHolder" + } + +} + +library class Pow_ extends @py_Pow, Operator { + + override string toString() { + result = "Pow" + } + +} + +library class Print_ extends @py_Print, Stmt { + + + /** Gets the destination of this print statement. */ + Expr getDest() { + py_exprs(result, _, this, 1) + } + + + /** Gets the values of this print statement. */ + ExprList getValues() { + py_expr_lists(result, this, 2) + } + + + /** Gets the nth value of this print statement. */ + Expr getValue(int index) { + result = this.getValues().getItem(index) + } + + /** Gets a value of this print statement. */ + Expr getAValue() { + result = this.getValues().getAnItem() + } + + + /** Whether the new line property of this print statement is true. */ + predicate isNl() { + py_bools(this, 3) + } + + override string toString() { + result = "Print" + } + +} + +library class RShift_ extends @py_RShift, Operator { + + override string toString() { + result = "RShift" + } + +} + +library class Raise_ extends @py_Raise, Stmt { + + + /** Gets the exception of this raise statement. */ + Expr getExc() { + py_exprs(result, _, this, 1) + } + + + /** Gets the cause of this raise statement. */ + Expr getCause() { + py_exprs(result, _, this, 2) + } + + + /** Gets the type of this raise statement. */ + Expr getType() { + py_exprs(result, _, this, 3) + } + + + /** Gets the instance of this raise statement. */ + Expr getInst() { + py_exprs(result, _, this, 4) + } + + + /** Gets the traceback of this raise statement. */ + Expr getTback() { + py_exprs(result, _, this, 5) + } + + override string toString() { + result = "Raise" + } + +} + +library class Repr_ extends @py_Repr, Expr { + + + /** Gets the value of this backtick expression. */ + Expr getValue() { + py_exprs(result, _, this, 2) + } + + override string toString() { + result = "Repr" + } + +} + +library class Return_ extends @py_Return, Stmt { + + + /** Gets the value of this return statement. */ + Expr getValue() { + py_exprs(result, _, this, 1) + } + + override string toString() { + result = "Return" + } + +} + +library class Set_ extends @py_Set, Expr { + + + /** Gets the elements of this set expression. */ + ExprList getElts() { + py_expr_lists(result, this, 2) + } + + + /** Gets the nth element of this set expression. */ + Expr getElt(int index) { + result = this.getElts().getItem(index) + } + + /** Gets an element of this set expression. */ + Expr getAnElt() { + result = this.getElts().getAnItem() + } + + override string toString() { + result = "Set" + } + +} + +library class SetComp_ extends @py_SetComp, Expr { + + + /** Gets the implementation of this set comprehension. */ + Function getFunction() { + py_Functions(result, this) + } + + + /** Gets the iterable of this set comprehension. */ + Expr getIterable() { + py_exprs(result, _, this, 3) + } + + override string toString() { + result = "SetComp" + } + +} + +library class Slice_ extends @py_Slice, Expr { + + + /** Gets the start of this slice. */ + Expr getStart() { + py_exprs(result, _, this, 2) + } + + + /** Gets the stop of this slice. */ + Expr getStop() { + py_exprs(result, _, this, 3) + } + + + /** Gets the step of this slice. */ + Expr getStep() { + py_exprs(result, _, this, 4) + } + + override string toString() { + result = "Slice" + } + +} + +library class Starred_ extends @py_Starred, Expr { + + + /** Gets the value of this starred expression. */ + Expr getValue() { + py_exprs(result, _, this, 2) + } + + + /** Gets the context of this starred expression. */ + ExprContext getCtx() { + py_expr_contexts(result, _, this) + } + + override string toString() { + result = "Starred" + } + +} + +library class Store_ extends @py_Store, ExprContext { + + override string toString() { + result = "Store" + } + +} + +library class Str_ extends @py_Str, Expr { + + + /** Gets the text of this string literal. */ + string getS() { + py_strs(result, this, 2) + } + + + /** Gets the prefix of this string literal. */ + string getPrefix() { + py_strs(result, this, 3) + } + + + /** Gets the implicitly_concatenated_parts of this string literal. */ + StringPartList getImplicitlyConcatenatedParts() { + py_StringPart_lists(result, this) + } + + + /** Gets the nth implicitly_concatenated_part of this string literal. */ + StringPart getImplicitlyConcatenatedPart(int index) { + result = this.getImplicitlyConcatenatedParts().getItem(index) + } + + /** Gets an implicitly_concatenated_part of this string literal. */ + StringPart getAnImplicitlyConcatenatedPart() { + result = this.getImplicitlyConcatenatedParts().getAnItem() + } + + override string toString() { + result = "Str" + } + +} + +library class StringPart_ extends @py_StringPart { + + + /** Gets the text of this implicitly concatenated part. */ + string getText() { + py_strs(result, this, 0) + } + + + /** Gets the location of this implicitly concatenated part. */ + Location getLocation() { + py_locations(result, this) + } + + StringPartList getParent() { + py_StringParts(this, result, _) + } + + string toString() { + result = "StringPart" + } + +} + +library class StringPartList_ extends @py_StringPart_list { + + BytesOrStr getParent() { + py_StringPart_lists(this, result) + } + + /** Gets an item of this implicitly concatenated part list */ + StringPart getAnItem() { + py_StringParts(result, this, _) + } + + /** Gets the nth item of this implicitly concatenated part list */ + StringPart getItem(int index) { + py_StringParts(result, this, index) + } + + string toString() { + result = "StringPartList" + } + +} + +library class Sub_ extends @py_Sub, Operator { + + override string toString() { + result = "Sub" + } + +} + +library class Subscript_ extends @py_Subscript, Expr { + + + /** Gets the value of this subscript expression. */ + Expr getValue() { + py_exprs(result, _, this, 2) + } + + + /** Gets the index of this subscript expression. */ + Expr getIndex() { + py_exprs(result, _, this, 3) + } + + + /** Gets the context of this subscript expression. */ + ExprContext getCtx() { + py_expr_contexts(result, _, this) + } + + override string toString() { + result = "Subscript" + } + +} + +library class TemplateDottedNotation_ extends @py_TemplateDottedNotation, Expr { + + + /** Gets the object of this template dotted notation expression. */ + Expr getValue() { + py_exprs(result, _, this, 2) + } + + + /** Gets the attribute name of this template dotted notation expression. */ + string getAttr() { + py_strs(result, this, 3) + } + + + /** Gets the context of this template dotted notation expression. */ + ExprContext getCtx() { + py_expr_contexts(result, _, this) + } + + override string toString() { + result = "TemplateDottedNotation" + } + +} + +library class TemplateWrite_ extends @py_TemplateWrite, Stmt { + + + /** Gets the value of this template write statement. */ + Expr getValue() { + py_exprs(result, _, this, 1) + } + + override string toString() { + result = "TemplateWrite" + } + +} + +library class Try_ extends @py_Try, Stmt { + + + /** Gets the body of this try statement. */ + StmtList getBody() { + py_stmt_lists(result, this, 1) + } + + + /** Gets the nth statement of this try statement. */ + Stmt getStmt(int index) { + result = this.getBody().getItem(index) + } + + /** Gets a statement of this try statement. */ + Stmt getAStmt() { + result = this.getBody().getAnItem() + } + + + /** Gets the else block of this try statement. */ + StmtList getOrelse() { + py_stmt_lists(result, this, 2) + } + + + /** Gets the nth else statement of this try statement. */ + Stmt getOrelse(int index) { + result = this.getOrelse().getItem(index) + } + + /** Gets an else statement of this try statement. */ + Stmt getAnOrelse() { + result = this.getOrelse().getAnItem() + } + + + /** Gets the exception handlers of this try statement. */ + StmtList getHandlers() { + py_stmt_lists(result, this, 3) + } + + + /** Gets the nth exception handler of this try statement. */ + Stmt getHandler(int index) { + result = this.getHandlers().getItem(index) + } + + /** Gets an exception handler of this try statement. */ + Stmt getAHandler() { + result = this.getHandlers().getAnItem() + } + + + /** Gets the finally block of this try statement. */ + StmtList getFinalbody() { + py_stmt_lists(result, this, 4) + } + + + /** Gets the nth finally statement of this try statement. */ + Stmt getFinalstmt(int index) { + result = this.getFinalbody().getItem(index) + } + + /** Gets a finally statement of this try statement. */ + Stmt getAFinalstmt() { + result = this.getFinalbody().getAnItem() + } + + override string toString() { + result = "Try" + } + +} + +library class Tuple_ extends @py_Tuple, Expr { + + + /** Gets the elements of this tuple expression. */ + ExprList getElts() { + py_expr_lists(result, this, 2) + } + + + /** Gets the nth element of this tuple expression. */ + Expr getElt(int index) { + result = this.getElts().getItem(index) + } + + /** Gets an element of this tuple expression. */ + Expr getAnElt() { + result = this.getElts().getAnItem() + } + + + /** Gets the context of this tuple expression. */ + ExprContext getCtx() { + py_expr_contexts(result, _, this) + } + + override ExprParent getParent() { + py_exprs(this, _, result, _) + } + + override string toString() { + result = "Tuple" + } + +} + +library class UAdd_ extends @py_UAdd, Unaryop { + + override string toString() { + result = "UAdd" + } + +} + +library class USub_ extends @py_USub, Unaryop { + + override string toString() { + result = "USub" + } + +} + +library class UnaryExpr_ extends @py_UnaryExpr, Expr { + + + /** Gets the operator of this unary expression. */ + Unaryop getOp() { + py_unaryops(result, _, this) + } + + + /** Gets the operand of this unary expression. */ + Expr getOperand() { + py_exprs(result, _, this, 3) + } + + override string toString() { + result = "UnaryExpr" + } + +} + +library class While_ extends @py_While, Stmt { + + + /** Gets the test of this while statement. */ + Expr getTest() { + py_exprs(result, _, this, 1) + } + + + /** Gets the body of this while statement. */ + StmtList getBody() { + py_stmt_lists(result, this, 2) + } + + + /** Gets the nth statement of this while statement. */ + Stmt getStmt(int index) { + result = this.getBody().getItem(index) + } + + /** Gets a statement of this while statement. */ + Stmt getAStmt() { + result = this.getBody().getAnItem() + } + + + /** Gets the else block of this while statement. */ + StmtList getOrelse() { + py_stmt_lists(result, this, 3) + } + + + /** Gets the nth else statement of this while statement. */ + Stmt getOrelse(int index) { + result = this.getOrelse().getItem(index) + } + + /** Gets an else statement of this while statement. */ + Stmt getAnOrelse() { + result = this.getOrelse().getAnItem() + } + + override string toString() { + result = "While" + } + +} + +library class With_ extends @py_With, Stmt { + + + /** Gets the context manager of this with statement. */ + Expr getContextExpr() { + py_exprs(result, _, this, 1) + } + + + /** Gets the optional variable of this with statement. */ + Expr getOptionalVars() { + py_exprs(result, _, this, 2) + } + + + /** Gets the body of this with statement. */ + StmtList getBody() { + py_stmt_lists(result, this, 3) + } + + + /** Gets the nth statement of this with statement. */ + Stmt getStmt(int index) { + result = this.getBody().getItem(index) + } + + /** Gets a statement of this with statement. */ + Stmt getAStmt() { + result = this.getBody().getAnItem() + } + + + /** Whether the async property of this with statement is true. */ + predicate isAsync() { + py_bools(this, 4) + } + + override string toString() { + result = "With" + } + +} + +library class Yield_ extends @py_Yield, Expr { + + + /** Gets the value of this yield expression. */ + Expr getValue() { + py_exprs(result, _, this, 2) + } + + override string toString() { + result = "Yield" + } + +} + +library class YieldFrom_ extends @py_YieldFrom, Expr { + + + /** Gets the value of this yield-from expression. */ + Expr getValue() { + py_exprs(result, _, this, 2) + } + + override string toString() { + result = "YieldFrom" + } + +} + +library class Alias_ extends @py_alias { + + + /** Gets the value of this alias. */ + Expr getValue() { + py_exprs(result, _, this, 0) + } + + + /** Gets the name of this alias. */ + Expr getAsname() { + py_exprs(result, _, this, 1) + } + + AliasList getParent() { + py_aliases(this, result, _) + } + + string toString() { + result = "Alias" + } + +} + +library class AliasList_ extends @py_alias_list { + + Import getParent() { + py_alias_lists(this, result) + } + + /** Gets an item of this alias list */ + Alias getAnItem() { + py_aliases(result, this, _) + } + + /** Gets the nth item of this alias list */ + Alias getItem(int index) { + py_aliases(result, this, index) + } + + string toString() { + result = "AliasList" + } + +} + +library class Arguments_ extends @py_arguments { + + + /** Gets the keyword default values of this parameters definition. */ + ExprList getKwDefaults() { + py_expr_lists(result, this, 0) + } + + + /** Gets the nth keyword default value of this parameters definition. */ + Expr getKwDefault(int index) { + result = this.getKwDefaults().getItem(index) + } + + /** Gets a keyword default value of this parameters definition. */ + Expr getAKwDefault() { + result = this.getKwDefaults().getAnItem() + } + + + /** Gets the default values of this parameters definition. */ + ExprList getDefaults() { + py_expr_lists(result, this, 1) + } + + + /** Gets the nth default value of this parameters definition. */ + Expr getDefault(int index) { + result = this.getDefaults().getItem(index) + } + + /** Gets a default value of this parameters definition. */ + Expr getADefault() { + result = this.getDefaults().getAnItem() + } + + + /** Gets the annotations of this parameters definition. */ + ExprList getAnnotations() { + py_expr_lists(result, this, 2) + } + + + /** Gets the nth annotation of this parameters definition. */ + Expr getAnnotation(int index) { + result = this.getAnnotations().getItem(index) + } + + /** Gets an annotation of this parameters definition. */ + Expr getAnAnnotation() { + result = this.getAnnotations().getAnItem() + } + + + /** Gets the *arg annotation of this parameters definition. */ + Expr getVarargannotation() { + py_exprs(result, _, this, 3) + } + + + /** Gets the **kwarg annotation of this parameters definition. */ + Expr getKwargannotation() { + py_exprs(result, _, this, 4) + } + + + /** Gets the kw_annotations of this parameters definition. */ + ExprList getKwAnnotations() { + py_expr_lists(result, this, 5) + } + + + /** Gets the nth kw_annotation of this parameters definition. */ + Expr getKwAnnotation(int index) { + result = this.getKwAnnotations().getItem(index) + } + + /** Gets a kw_annotation of this parameters definition. */ + Expr getAKwAnnotation() { + result = this.getKwAnnotations().getAnItem() + } + + ArgumentsParent getParent() { + py_arguments(this, result) + } + + string toString() { + result = "Arguments" + } + +} + +library class ArgumentsParent_ extends @py_arguments_parent { + + string toString() { + result = "ArgumentsParent" + } + +} + +library class AstNode_ extends @py_ast_node { + + string toString() { + result = "AstNode" + } + +} + +library class BoolParent_ extends @py_bool_parent { + + string toString() { + result = "BoolParent" + } + +} + +library class Boolop_ extends @py_boolop { + + BoolExpr getParent() { + py_boolops(this, _, result) + } + + string toString() { + result = "Boolop" + } + +} + +library class Cmpop_ extends @py_cmpop { + + CmpopList getParent() { + py_cmpops(this, _, result, _) + } + + string toString() { + result = "Cmpop" + } + +} + +library class CmpopList_ extends @py_cmpop_list { + + Compare getParent() { + py_cmpop_lists(this, result) + } + + /** Gets an item of this comparison operator list */ + Cmpop getAnItem() { + py_cmpops(result, _, this, _) + } + + /** Gets the nth item of this comparison operator list */ + Cmpop getItem(int index) { + py_cmpops(result, _, this, index) + } + + string toString() { + result = "CmpopList" + } + +} + +library class Comprehension_ extends @py_comprehension { + + + /** Gets the location of this comprehension. */ + Location getLocation() { + py_locations(result, this) + } + + + /** Gets the iterable of this comprehension. */ + Expr getIter() { + py_exprs(result, _, this, 1) + } + + + /** Gets the target of this comprehension. */ + Expr getTarget() { + py_exprs(result, _, this, 2) + } + + + /** Gets the conditions of this comprehension. */ + ExprList getIfs() { + py_expr_lists(result, this, 3) + } + + + /** Gets the nth condition of this comprehension. */ + Expr getIf(int index) { + result = this.getIfs().getItem(index) + } + + /** Gets a condition of this comprehension. */ + Expr getAnIf() { + result = this.getIfs().getAnItem() + } + + ComprehensionList getParent() { + py_comprehensions(this, result, _) + } + + string toString() { + result = "Comprehension" + } + +} + +library class ComprehensionList_ extends @py_comprehension_list { + + ListComp getParent() { + py_comprehension_lists(this, result) + } + + /** Gets an item of this comprehension list */ + Comprehension getAnItem() { + py_comprehensions(result, this, _) + } + + /** Gets the nth item of this comprehension list */ + Comprehension getItem(int index) { + py_comprehensions(result, this, index) + } + + string toString() { + result = "ComprehensionList" + } + +} + +library class DictItem_ extends @py_dict_item { + + DictItemList getParent() { + py_dict_items(this, _, result, _) + } + + string toString() { + result = "DictItem" + } + +} + +library class DictItemList_ extends @py_dict_item_list { + + DictItemListParent getParent() { + py_dict_item_lists(this, result) + } + + /** Gets an item of this dict_item list */ + DictItem getAnItem() { + py_dict_items(result, _, this, _) + } + + /** Gets the nth item of this dict_item list */ + DictItem getItem(int index) { + py_dict_items(result, _, this, index) + } + + string toString() { + result = "DictItemList" + } + +} + +library class DictItemListParent_ extends @py_dict_item_list_parent { + + string toString() { + result = "DictItemListParent" + } + +} + +library class Expr_ extends @py_expr { + + + /** Gets the location of this expression. */ + Location getLocation() { + py_locations(result, this) + } + + + /** Whether the parenthesised property of this expression is true. */ + predicate isParenthesised() { + py_bools(this, 1) + } + + ExprParent getParent() { + py_exprs(this, _, result, _) + } + + string toString() { + result = "Expr" + } + +} + +library class ExprContext_ extends @py_expr_context { + + ExprContextParent getParent() { + py_expr_contexts(this, _, result) + } + + string toString() { + result = "ExprContext" + } + +} + +library class ExprContextParent_ extends @py_expr_context_parent { + + string toString() { + result = "ExprContextParent" + } + +} + +library class ExprList_ extends @py_expr_list { + + ExprListParent getParent() { + py_expr_lists(this, result, _) + } + + /** Gets an item of this expression list */ + Expr getAnItem() { + py_exprs(result, _, this, _) + } + + /** Gets the nth item of this expression list */ + Expr getItem(int index) { + py_exprs(result, _, this, index) + } + + string toString() { + result = "ExprList" + } + +} + +library class ExprListParent_ extends @py_expr_list_parent { + + string toString() { + result = "ExprListParent" + } + +} + +library class ExprOrStmt_ extends @py_expr_or_stmt { + + string toString() { + result = "ExprOrStmt" + } + +} + +library class ExprParent_ extends @py_expr_parent { + + string toString() { + result = "ExprParent" + } + +} + +library class Keyword_ extends @py_keyword, DictItem { + + + /** Gets the location of this keyword argument. */ + override Location getLocation() { + py_locations(result, this) + } + + + /** Gets the value of this keyword argument. */ + Expr getValue() { + py_exprs(result, _, this, 1) + } + + + /** Gets the arg of this keyword argument. */ + string getArg() { + py_strs(result, this, 2) + } + + override string toString() { + result = "Keyword" + } + +} + +library class LocationParent_ extends @py_location_parent { + + string toString() { + result = "LocationParent" + } + +} + +library class Operator_ extends @py_operator { + + BinaryExpr getParent() { + py_operators(this, _, result) + } + + string toString() { + result = "Operator" + } + +} + +library class Parameter_ extends @py_parameter { + + string toString() { + result = "Parameter" + } + +} + +library class Scope_ extends @py_scope { + + string toString() { + result = "Scope" + } + +} + +library class Stmt_ extends @py_stmt { + + + /** Gets the location of this statement. */ + Location getLocation() { + py_locations(result, this) + } + + StmtList getParent() { + py_stmts(this, _, result, _) + } + + string toString() { + result = "Stmt" + } + +} + +library class StmtList_ extends @py_stmt_list { + + StmtListParent getParent() { + py_stmt_lists(this, result, _) + } + + /** Gets an item of this statement list */ + Stmt getAnItem() { + py_stmts(result, _, this, _) + } + + /** Gets the nth item of this statement list */ + Stmt getItem(int index) { + py_stmts(result, _, this, index) + } + + string toString() { + result = "StmtList" + } + +} + +library class StmtListParent_ extends @py_stmt_list_parent { + + string toString() { + result = "StmtListParent" + } + +} + +library class StringList_ extends @py_str_list { + + StrListParent getParent() { + py_str_lists(this, result) + } + + /** Gets an item of this string list */ + string getAnItem() { + py_strs(result, this, _) + } + + /** Gets the nth item of this string list */ + string getItem(int index) { + py_strs(result, this, index) + } + + string toString() { + result = "StringList" + } + +} + +library class StrListParent_ extends @py_str_list_parent { + + string toString() { + result = "StrListParent" + } + +} + +library class StrParent_ extends @py_str_parent { + + string toString() { + result = "StrParent" + } + +} + +library class Unaryop_ extends @py_unaryop { + + UnaryExpr getParent() { + py_unaryops(this, _, result) + } + + string toString() { + result = "Unaryop" + } + +} + +library class VariableParent_ extends @py_variable_parent { + + string toString() { + result = "VariableParent" + } + +} + diff --git a/python/ql/src/semmle/python/Class.qll b/python/ql/src/semmle/python/Class.qll new file mode 100644 index 000000000000..1f754536bdde --- /dev/null +++ b/python/ql/src/semmle/python/Class.qll @@ -0,0 +1,219 @@ +import python + + +/** An (artificial) expression corresponding to a class definition. + * It is recommended to use `ClassDef` instead. + */ +class ClassExpr extends ClassExpr_ { + + /** Gets the metaclass expression */ + Expr getMetaClass() { + if major_version() = 3 then + exists(Keyword metacls | this.getAKeyword() = metacls and metacls.getArg() = "metaclass" and result = metacls.getValue()) + else + exists(Assign a | a = this.getInnerScope().getAStmt() and ((Name)a.getATarget()).getId() = "__metaclass__" and result = a.getValue()) + } + + + /** Gets the nth keyword argument of this class definition. */ + override DictUnpackingOrKeyword getKeyword(int index) { + result = this.getKeywords().getItem(index) + } + + /** Gets a keyword argument of this class definition. */ + override DictUnpackingOrKeyword getAKeyword() { + result = this.getKeywords().getAnItem() + } + + override Expr getASubExpression() { + result = this.getABase() or + result = this.getAKeyword().getValue() or + result = this.getKwargs() or + result = this.getStarargs() + } + + Call getADecoratorCall() { + result.getArg(0) = this or + result.getArg(0) = this.getADecoratorCall() + } + + /** Gets a decorator of this function expression */ + Expr getADecorator() { + result = this.getADecoratorCall().getFunc() + } + + override AstNode getAChildNode() { + result = this.getASubExpression() + or + result = this.getInnerScope() + } + + /** Gets a tuple (*) argument of this class definition. */ + Expr getStarargs() { + result = this.getABase().(Starred).getValue() + } + + /** Gets a dictionary (**) argument of this class definition. */ + Expr getKwargs() { + result = this.getAKeyword().(DictUnpacking).getValue() + } + +} + +/** A class statement. Note that ClassDef extends Assign as a class definition binds the newly created class */ +class ClassDef extends Assign { + + ClassDef() { + /* This is an artificial assignment the rhs of which is a (possibly decorated) ClassExpr */ + exists(ClassExpr c | this.getValue() = c or this.getValue() = c.getADecoratorCall()) + } + + override string toString() { + result = "ClassDef" + } + + /** Gets the class for this statement */ + Class getDefinedClass() { + exists(ClassExpr c | + this.getValue() = c or this.getValue() = c.getADecoratorCall() | + result = c.getInnerScope() + ) + } + + override Stmt getLastStatement() { + result = this.getDefinedClass().getLastStatement() + } + +} + +/** The scope of a class. This is the scope of all the statements within the class definition */ +class Class extends Class_, Scope, AstNode { + + /** Use getADecorator() instead of getDefinition().getADecorator() + * Use getMetaClass() instead of getDefinition().getMetaClass() + */ + deprecated ClassExpr getDefinition() { + result = this.getParent() + } + + /** Gets a defined init method of this class */ + Function getInitMethod() { + result.getScope() = this and result.isInitMethod() + } + + /** Gets a method defined in this class */ + Function getAMethod() { + result.getScope() = this + } + + override Location getLocation() { + py_scope_location(result, this) + } + + /** Gets the scope (module, class or function) in which this class is defined */ + override Scope getEnclosingScope() { + result = this.getParent().getScope() + } + + /** Use getEnclosingScope() instead */ + override Scope getScope() { + result = this.getParent().getScope() + } + + override string toString() { + result = "Class " + this.getName() + } + + /** Gets the statements forming the body of this class */ + override StmtList getBody() { + result = Class_.super.getBody() + } + + /** Gets the nth statement in the class */ + override Stmt getStmt(int index) { + result = Class_.super.getStmt(index) + } + + /** Gets a statement in the class */ + override Stmt getAStmt() { + result = Class_.super.getAStmt() + } + + /** Gets the name used to define this class */ + override string getName() { + result = Class_.super.getName() + } + + predicate hasSideEffects() { + any() + } + + /** Whether this is probably a mixin (has 'mixin' or similar in name or docstring) */ + predicate isProbableMixin() { + (this.getName().toLowerCase().matches("%mixin%") + or + this.getDocString().getText().toLowerCase().matches("%mixin%") + or + this.getDocString().getText().toLowerCase().matches("%mix-in%") + ) + } + + override AstNode getAChildNode() { + result = this.getAStmt() + } + + Expr getADecorator() { + result = this.getParent().getADecorator() + } + + /** Gets the metaclass expression */ + Expr getMetaClass() { + result = this.getParent().getMetaClass() + } + + /** Gets the ClassObject corresponding to this class */ + ClassObject getClassObject() { + result.getOrigin() = this.getParent() + } + + /** Gets the nth base of this class definition. */ + Expr getBase(int index) { + result = this.getParent().getBase(index) + } + + /** Gets a base of this class definition. */ + Expr getABase() { + result = this.getParent().getABase() + } + + /** Gets the metrics for this class */ + ClassMetrics getMetrics() { + result = this + } + + /** Gets the qualified name for this class. + * Should return the same name as the `__qualname__` attribute on classes in Python 3. + */ + string getQualifiedName() { + this.getScope() instanceof Module and result = this.getName() + or + exists(string enclosing_name | + enclosing_name = this.getScope().(Function).getQualifiedName() + or + enclosing_name = this.getScope().(Class).getQualifiedName() | + result = enclosing_name + "." + this.getName() + ) + } + + override + predicate containsInScope(AstNode inner) { + Scope.super.containsInScope(inner) + } + + override + predicate contains(AstNode inner) { + Scope.super.contains(inner) + } + +} + diff --git a/python/ql/src/semmle/python/Comment.qll b/python/ql/src/semmle/python/Comment.qll new file mode 100644 index 000000000000..c40de34478bb --- /dev/null +++ b/python/ql/src/semmle/python/Comment.qll @@ -0,0 +1,110 @@ +import python + +/** A source code comment */ +class Comment extends @py_comment { + + /** Gets the full text of the comment including the leading '#' */ + string getText() { + py_comments(this, result, _) + } + + /** Gets the contents of the comment excluding the leading '#' */ + string getContents() { + result = this.getText().suffix(1) + } + + Location getLocation() { + py_comments(this, _, result) + + } + + string toString() { + result = "Comment " + this.getText() + } + + /** Gets this immediately following comment. + * Blanks line are allowed between this comment and the following comment, + * but code or other comments are not. + */ + Comment getFollowing() { + exists(File f, int n | + this.file_line(f, n) | + result.file_line(f, n+1) + or + result.file_line(f, n+2) and f.emptyLine(n+1) + or + result.file_line(f, n+3) and f.emptyLine(n+2) and f.emptyLine(n+1) + ) + } + + private predicate file_line(File f, int n) { + this.getLocation().getFile() = f and + this.getLocation().getStartLine() = n + } + +} + +private predicate comment_block_part(Comment start, Comment part, int i) { + not exists(Comment prev | prev.getFollowing() = part) and + exists(Comment following | part.getFollowing() = following) and + start = part and i = 1 + or + exists(Comment prev | + comment_block_part(start, prev, i-1) and + part = prev.getFollowing() + ) +} + +/** A block of consecutive comments */ +class CommentBlock extends @py_comment { + + CommentBlock() { + comment_block_part(this, _, _) + } + + private Comment last() { + comment_block_part(this, result, this.length()) + } + + string toString() { + result = "Comment block" + } + + /** The length of this comment block (in comments) */ + int length() { + result = max(int i | comment_block_part(this, _, i)) + } + + predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { + ((Comment)this).getLocation().hasLocationInfo(filepath, bl, bc, _, _) + and + exists(Comment end | + end = this.last() | + end.getLocation().hasLocationInfo(_, _, _, el, ec) + ) + } + + predicate contains(Comment c) { + comment_block_part(this, c, _) + or + this = c + } + + string getContents() { + result = concat(Comment c,int i | + comment_block_part(this, c, i) or this = c and i = 0 | + c.getContents() order by i + ) + } + +} + +/** A type-hint comment. Any comment that starts with `# type:` */ +class TypeHintComment extends Comment { + + TypeHintComment() { + this.getText().regexpMatch("# +type:.*") + } + +} + diff --git a/python/ql/src/semmle/python/Comparisons.qll b/python/ql/src/semmle/python/Comparisons.qll new file mode 100644 index 000000000000..4967456e7bed --- /dev/null +++ b/python/ql/src/semmle/python/Comparisons.qll @@ -0,0 +1,482 @@ + +import python + +/* A class representing the six comparison operators, ==, !=, <, <=, > and >=. + * */ +class CompareOp extends int { + + CompareOp() { + this in [1..6] + } + + /** Gets the logical inverse operator */ + CompareOp invert() { + this = eq() and result = ne() or + this = ne() and result = eq() or + this = lt() and result = ge() or + this = gt() and result = le() or + this = le() and result = gt() or + this = ge() and result = lt() + } + + /** Gets the reverse operator (swapping the operands) */ + CompareOp reverse() { + this = eq() and result = eq() or + this = ne() and result = ne() or + this = lt() and result = gt() or + this = gt() and result = lt() or + this = le() and result = ge() or + this = ge() and result = le() + } + + string repr() { + this = eq() and result = "==" or + this = ne() and result = "!=" or + this = lt() and result = "<" or + this = gt() and result = ">" or + this = le() and result = "<=" or + this = ge() and result = ">=" + } + + predicate forOp(Cmpop op) { + op instanceof Eq and this = eq() or + op instanceof NotEq and this = ne() or + op instanceof Lt and this = lt() or + op instanceof LtE and this = le() or + op instanceof Gt and this = gt() or + op instanceof GtE and this = ge() + } + + /** Return this if isTrue is true, otherwise returns the inverse */ + CompareOp conditional(boolean isTrue) { + result = this and isTrue = true + or + result = this.invert() and isTrue = false + } + +} + +CompareOp eq() { result = 1 } +CompareOp ne() { result = 2 } +CompareOp lt() { result = 3 } +CompareOp le() { result = 4 } +CompareOp gt() { result = 5 } +CompareOp ge() { result = 6 } + +/* Workaround precision limits in floating point numbers */ +bindingset[x] private predicate ok_magnitude(float x) { + x > -9007199254740992.0 // -2**53 + and + x < 9007199254740992.0 // 2**53 +} + +bindingset[x,y] private float add(float x, float y) { + ok_magnitude(x) and + ok_magnitude(y) and + ok_magnitude(result) and + result = x + y +} + +bindingset[x,y] private float sub(float x, float y) { + ok_magnitude(x) and + ok_magnitude(y) and + ok_magnitude(result) and + result = x - y +} + +/** Normalise equality cmp into the form `left op right + k`. */ +private predicate test(ControlFlowNode cmp, ControlFlowNode left, CompareOp op, ControlFlowNode right, float k) { + simple_test(cmp, left, op, right) and k = 0 + or + add_test(cmp, left, op, right, k) + or + not_test(cmp, left, op, right, k) + or + subtract_test(cmp, left, op, right, k) + or + exists(float c | test(cmp, right, op.reverse(), left, c) and k = -c) +} + +/** Various simple tests in left op right + k form. */ +private predicate simple_test(CompareNode cmp, ControlFlowNode l, CompareOp cmpop, ControlFlowNode r) { + exists(Cmpop op | + cmp.operands(l, op, r) and cmpop.forOp(op) + ) +} + +private predicate add_test_left(CompareNode cmp, ControlFlowNode l, CompareOp op, ControlFlowNode r, float k) { + exists(BinaryExprNode lhs, float c, float x, Num n | + lhs.getNode().getOp() instanceof Add and + test(cmp, lhs, op, r, c) and x = n.getN().toFloat() and k = sub(c, x) | + l = lhs.getLeft() and n = lhs.getRight().getNode() + or + l = lhs.getRight() and n = lhs.getLeft().getNode() + ) +} + +private predicate add_test_right(CompareNode cmp, ControlFlowNode l, CompareOp op, ControlFlowNode r, float k) { + exists(BinaryExprNode rhs, float c, float x, Num n | + rhs.getNode().getOp() instanceof Add and + test(cmp, l, op, rhs, c) and x = n.getN().toFloat() and k = add(c, x) | + r = rhs.getLeft() and n = rhs.getRight().getNode() + or + r = rhs.getRight() and n = rhs.getLeft().getNode() + ) +} + +/* left + x op right + c => left op right + (c-x) + left op (right + x) + c => left op right + (c+x) */ +private predicate add_test(CompareNode cmp, ControlFlowNode l, CompareOp op, ControlFlowNode r, float k) { + add_test_left(cmp, l, op, r, k) + or + add_test_right(cmp, l, op ,r, k) +} + +private predicate subtract_test_left(CompareNode cmp, ControlFlowNode l, CompareOp op, ControlFlowNode r, float k) { + exists(BinaryExprNode lhs, float c, float x, Num n | + lhs.getNode().getOp() instanceof Sub and + test(cmp, lhs, op, r, c) and + l = lhs.getLeft() and n = lhs.getRight().getNode() and + x = n.getN().toFloat() | + k = add(c, x) + ) +} + +private predicate subtract_test_right(CompareNode cmp, ControlFlowNode l, CompareOp op, ControlFlowNode r, float k) { + exists(BinaryExprNode rhs, float c, float x, Num n | + rhs.getNode().getOp() instanceof Sub and + test(cmp, l, op, rhs, c) and + r = rhs.getRight() and n = rhs.getLeft().getNode() and + x = n.getN().toFloat() | + k = sub(c, x) + ) +} + +/* left - x op right + c => left op right + (c+x) + left op (right - x) + c => left op right + (c-x) */ +private predicate subtract_test(CompareNode cmp, ControlFlowNode l, CompareOp op, ControlFlowNode r, float k) { + subtract_test_left(cmp, l, op, r, k) + or + subtract_test_right(cmp, l, op, r, k) +} + +private predicate not_test(UnaryExprNode u, ControlFlowNode l, CompareOp op, ControlFlowNode r, float k) { + u.getNode().getOp() instanceof Not + and + test(u.getOperand(), l, op.invert(), r, k) +} + + +/** A comparison which can be simplified to the canonical form `x OP y + k` where `x` and `y` are `ControlFlowNode`s, + * `k` is a floating point constant and `OP` is one of `<=`, `>`, `==` or `!=`. + */ +class Comparison extends ControlFlowNode { + + Comparison() { + test(this, _, _, _, _) + } + + /** Whether this condition tests `l op r + k` */ + predicate tests(ControlFlowNode l, CompareOp op, ControlFlowNode r, float k) { + test(this, l, op, r, k) + } + + /** Whether this condition tests `l op k` */ + predicate tests(ControlFlowNode l, CompareOp op, float k) { + exists(ControlFlowNode r, float x, float c | + test(this, l, op, r, c) | + x = r.getNode().(Num).getN().toFloat() and + k = add(c, x) + ) + } + + /* The following predicates determine whether this test, when its result is `thisIsTrue`, + * is equivalent to the predicate `v OP k` or `v1 OP v2 + k`. + * For example, the test `x <= y` being false, is equivalent to the predicate `x > y`. + */ + + private predicate equivalentToEq(boolean thisIsTrue, SsaVariable v, float k) { + this.tests(v.getAUse(), eq().conditional(thisIsTrue), k) + } + + private predicate equivalentToNotEq(boolean thisIsTrue, SsaVariable v, float k) { + this.tests(v.getAUse(), ne().conditional(thisIsTrue), k) + } + + private predicate equivalentToLt(boolean thisIsTrue, SsaVariable v, float k) { + this.tests(v.getAUse(), lt().conditional(thisIsTrue), k) + } + + private predicate equivalentToLtEq(boolean thisIsTrue, SsaVariable v, float k) { + this.tests(v.getAUse(), le().conditional(thisIsTrue), k) + } + + private predicate equivalentToGt(boolean thisIsTrue, SsaVariable v, float k) { + this.tests(v.getAUse(), gt().conditional(thisIsTrue), k) + } + + private predicate equivalentToGtEq(boolean thisIsTrue, SsaVariable v, float k) { + this.tests(v.getAUse(), ge().conditional(thisIsTrue), k) + } + + private predicate equivalentToEq(boolean thisIsTrue, SsaVariable v1, SsaVariable v2, float k) { + this.tests(v1.getAUse(), eq().conditional(thisIsTrue), v2.getAUse(), k) + } + + private predicate equivalentToNotEq(boolean thisIsTrue, SsaVariable v1, SsaVariable v2, float k) { + this.tests(v1.getAUse(), ne().conditional(thisIsTrue), v2.getAUse(), k) + } + + private predicate equivalentToLt(boolean thisIsTrue, SsaVariable v1, SsaVariable v2, float k) { + this.tests(v1.getAUse(), lt().conditional(thisIsTrue), v2.getAUse(), k) + } + + private predicate equivalentToLtEq(boolean thisIsTrue, SsaVariable v1, SsaVariable v2, float k) { + this.tests(v1.getAUse(), le().conditional(thisIsTrue), v2.getAUse(), k) + } + + private predicate equivalentToGt(boolean thisIsTrue, SsaVariable v1, SsaVariable v2, float k) { + this.tests(v1.getAUse(), gt().conditional(thisIsTrue), v2.getAUse(), k) + } + + private predicate equivalentToGtEq(boolean thisIsTrue, SsaVariable v1, SsaVariable v2, float k) { + this.tests(v1.getAUse(), ge().conditional(thisIsTrue), v2.getAUse(), k) + } + + /** Whether the result of this comparison being `thisIsTrue` implies that the result of `that` is `isThatTrue`. + * In other words, does the predicate that is equivalent to the result of `this` being `thisIsTrue` + * imply the predicate that is equivalent to the result of `that` being `thatIsTrue`. + * For example, assume that there are two tests, which when normalised have the form `x < y` and `x > y + 1`. + * Then the test `x < y` having a true result, implies that the test `x > y + 1` will have a false result. + * (`x < y` having a false result implies nothing about `x > y + 1`) + */ + predicate impliesThat(boolean thisIsTrue, Comparison that, boolean thatIsTrue) { + /* `v == k` => `v == k` */ + exists(SsaVariable v, float k1, float k2 | + this.equivalentToEq(thisIsTrue, v, k1) and + that.equivalentToEq(thatIsTrue, v, k2) and + eq(k1, k2) + or + this.equivalentToNotEq(thisIsTrue, v, k1) and + that.equivalentToNotEq(thatIsTrue, v, k2) and + eq(k1, k2) + ) + or + exists(SsaVariable v, float k1, float k2 | + /* `v < k1` => `v != k2` iff k1 <= k2 */ + this.equivalentToLt(thisIsTrue, v, k1) and + that.equivalentToNotEq(thatIsTrue, v, k2) and + le(k1, k2) + or + /* `v <= k1` => `v != k2` iff k1 < k2 */ + this.equivalentToLtEq(thisIsTrue, v, k1) and + that.equivalentToNotEq(thatIsTrue, v, k2) and + lt(k1, k2) + or + /* `v > k1` => `v != k2` iff k1 >= k2 */ + this.equivalentToGt(thisIsTrue, v, k1) and + that.equivalentToNotEq(thatIsTrue, v, k2) and + ge(k1, k2) + or + /* `v >= k1` => `v != k2` iff k1 > k2 */ + this.equivalentToGtEq(thisIsTrue, v, k1) and + that.equivalentToNotEq(thatIsTrue, v, k2) and + gt(k1, k2) + ) + or + exists(SsaVariable v, float k1, float k2 | + /* `v < k1` => `v < k2` iff k1 <= k2 */ + this.equivalentToLt(thisIsTrue, v, k1) and + that.equivalentToLt(thatIsTrue, v, k2) and + le(k1, k2) + or + /* `v < k1` => `v <= k2` iff k1 <= k2 */ + this.equivalentToLt(thisIsTrue, v, k1) and + that.equivalentToLtEq(thatIsTrue, v, k2) and + le(k1, k2) + or + /* `v <= k1` => `v < k2` iff k1 < k2 */ + this.equivalentToLtEq(thisIsTrue, v, k1) and + that.equivalentToLt(thatIsTrue, v, k2) and + lt(k1, k2) + or + /* `v <= k1` => `v <= k2` iff k1 <= k2 */ + this.equivalentToLtEq(thisIsTrue, v, k1) and + that.equivalentToLtEq(thatIsTrue, v, k2) and + le(k1, k2) + ) + or + exists(SsaVariable v, float k1, float k2 | + /* `v > k1` => `v >= k2` iff k1 >= k2 */ + this.equivalentToGt(thisIsTrue, v, k1) and + that.equivalentToGt(thatIsTrue, v, k2) and + ge(k1, k2) + or + /* `v > k1` => `v >= k2` iff k1 >= k2 */ + this.equivalentToGt(thisIsTrue, v, k1) and + that.equivalentToGtEq(thatIsTrue, v, k2) and + ge(k1, k2) + or + /* `v >= k1` => `v > k2` iff k1 > k2 */ + this.equivalentToGtEq(thisIsTrue, v, k1) and + that.equivalentToGt(thatIsTrue, v, k2) and + gt(k1, k2) + or + /* `v >= k1` => `v >= k2` iff k1 >= k2 */ + this.equivalentToGtEq(thisIsTrue, v, k1) and + that.equivalentToGtEq(thatIsTrue, v, k2) and + ge(k1, k2) + ) + or + exists(SsaVariable v1, SsaVariable v2, float k | + /* `v1 == v2 + k` => `v1 == v2 + k` */ + this.equivalentToEq(thisIsTrue, v1, v2, k) and + that.equivalentToEq(thatIsTrue, v1, v2, k) + or + this.equivalentToNotEq(thisIsTrue, v1, v2, k) and + that.equivalentToNotEq(thatIsTrue, v1, v2, k) + ) + or + exists(SsaVariable v1, SsaVariable v2, float k1, float k2 | + /* `v1 < v2 + k1` => `v1 != v2 + k2` iff k1 <= k2 */ + this.equivalentToLt(thisIsTrue, v1, v2, k1) and + that.equivalentToNotEq(thatIsTrue, v1, v2, k2) and + le(k1, k2) + or + /* `v1 <= v2 + k1` => `v1 != v2 + k2` iff k1 < k2 */ + this.equivalentToLtEq(thisIsTrue, v1, v2, k1) and + that.equivalentToNotEq(thatIsTrue, v1, v2, k2) and + lt(k1, k2) + or + /* `v1 > v2 + k1` => `v1 != v2 + k2` iff k1 >= k2 */ + this.equivalentToGt(thisIsTrue, v1, v2, k1) and + that.equivalentToNotEq(thatIsTrue, v1, v2, k2) and + ge(k1, k2) + or + /* `v1 >= v2 + k1` => `v1 != v2 + k2` iff k1 > k2 */ + this.equivalentToGtEq(thisIsTrue, v1, v2, k1) and + that.equivalentToNotEq(thatIsTrue, v1, v2, k2) and + gt(k1, k2) + ) + or + exists(SsaVariable v1, SsaVariable v2, float k1, float k2 | + /* `v1 <= v2 + k1` => `v1 <= v2 + k2` iff k1 <= k2 */ + this.equivalentToLtEq(thisIsTrue, v1, v2, k1) and + that.equivalentToLtEq(thatIsTrue, v1, v2, k2) and + le(k1, k2) + or + /* `v1 < v2 + k1` => `v1 <= v2 + k2` iff k1 <= k2 */ + this.equivalentToLt(thisIsTrue, v1, v2, k1) and + that.equivalentToLtEq(thatIsTrue, v1, v2, k2) and + le(k1, k2) + or + /* `v1 <= v2 + k1` => `v1 < v2 + k2` iff k1 < k2 */ + this.equivalentToLtEq(thisIsTrue, v1, v2, k1) and + that.equivalentToLt(thatIsTrue, v1, v2, k2) and + lt(k1, k2) + or + /* `v1 <= v2 + k1` => `v1 <= v2 + k2` iff k1 <= k2 */ + this.equivalentToLtEq(thisIsTrue, v1, v2, k1) and + that.equivalentToLtEq(thatIsTrue, v1, v2, k2) and + le(k1, k2) + ) + or + exists(SsaVariable v1, SsaVariable v2, float k1, float k2 | + /* `v1 > v2 + k1` => `v1 > v2 + k2` iff k1 >= k2 */ + this.equivalentToGt(thisIsTrue, v1, v2, k1) and + that.equivalentToGt(thatIsTrue, v1, v2, k2) and + ge(k1, k2) + or + /* `v1 > v2 + k1` => `v2 >= v2 + k2` iff k1 >= k2 */ + this.equivalentToGt(thisIsTrue, v1, v2, k1) and + that.equivalentToGtEq(thatIsTrue, v1, v2, k2) and + ge(k1, k2) + or + /* `v1 >= v2 + k1` => `v2 > v2 + k2` iff k1 > k2 */ + this.equivalentToGtEq(thisIsTrue, v1, v2, k1) and + that.equivalentToGt(thatIsTrue, v1, v2, k2) and + gt(k1, k2) + or + /* `v1 >= v2 + k1` => `v2 >= v2 + k2` iff k1 >= k2 */ + this.equivalentToGtEq(thisIsTrue, v1, v2, k1) and + that.equivalentToGtEq(thatIsTrue, v1, v2, k2) and + ge(k1, k2) + ) + } + +} + +/* Work around differences in floating-point comparisons between Python and QL */ +private predicate is_zero(float x) { + x = 0.0 + or + x = -0.0 +} + +bindingset[x,y] private predicate lt(float x, float y) { + if is_zero(x) then + y > 0 + else + x < y +} + +bindingset[x,y] private predicate eq(float x, float y) { + if is_zero(x) then + is_zero(y) + else + x = y +} + +bindingset[x,y] private predicate gt(float x, float y) { + lt(y, x) +} + +bindingset[x,y] private predicate le(float x, float y) { + lt(x, y) or eq(x, y) +} + +bindingset[x,y] private predicate ge(float x, float y) { + lt(y, x) or eq(x, y) +} + + +/** A basic block which terminates in a condition, splitting the subsequent control flow, + * in which the condition is an instance of `Comparison` + */ +class ComparisonControlBlock extends ConditionBlock { + + ComparisonControlBlock() { + this.getLastNode() instanceof Comparison + } + + /** Whether this conditional guard determines that, in block `b`, `l == r + k` if `eq` is true, or `l != r + k` if `eq` is false, */ + predicate controls(ControlFlowNode l, CompareOp op, ControlFlowNode r, float k, BasicBlock b) { + exists(boolean control | + this.controls(b, control) and this.getTest().tests(l, op, r, k) and control = true + or + this.controls(b, control) and this.getTest().tests(l, op.invert(), r, k) and control = false + ) + } + + /** Whether this conditional guard determines that, in block `b`, `l == r + k` if `eq` is true, or `l != r + k` if `eq` is false, */ + predicate controls(ControlFlowNode l, CompareOp op, float k, BasicBlock b) { + exists(boolean control | + this.controls(b, control) and this.getTest().tests(l, op, k) and control = true + or + this.controls(b, control) and this.getTest().tests(l, op.invert(), k) and control = false + ) + } + + Comparison getTest() { + this.getLastNode() = result + } + + /** Whether this conditional guard implies that, in block `b`, the result of `that` is `thatIsTrue` */ + predicate impliesThat(BasicBlock b, Comparison that, boolean thatIsTrue) { + exists(boolean controlSense | + this.controls(b, controlSense) and + this.getTest().impliesThat(controlSense, that, thatIsTrue) + ) + } + +} diff --git a/python/ql/src/semmle/python/Comprehensions.qll b/python/ql/src/semmle/python/Comprehensions.qll new file mode 100644 index 000000000000..eec5dd372f69 --- /dev/null +++ b/python/ql/src/semmle/python/Comprehensions.qll @@ -0,0 +1,154 @@ +import python + +/** Base class for list, set and dictionary comprehensions, and generator expressions. */ +abstract class Comp extends Expr { + + abstract Function getFunction(); + + /** Gets the iteration variable for the nth innermost generator of this list comprehension */ + Variable getIterationVariable(int n) { + result.getAnAccess() = this.getNthInnerLoop(n).getTarget() + } + + private For getNthInnerLoop(int n) { + n = 0 and result = this.getFunction().getStmt(0) + or + result = this.getNthInnerLoop(n-1).getStmt(0) + } + + /** Gets the iteration variable for a generator of this list comprehension */ + Variable getAnIterationVariable() { + result = this.getIterationVariable(_) + } + + /** Gets the scope in which the body of this list comprehension evaluates. */ + Scope getEvaluatingScope() { + result = this.getFunction() + } + + /** Gets the expression for elements of this comprehension. */ + Expr getElt() { + exists(Yield yield, Stmt body | + result = yield.getValue() and + body = this.getNthInnerLoop(_).getAStmt() | + yield = body.(ExprStmt).getValue() + or + yield = body.(If).getStmt(0).(ExprStmt).getValue() + ) + } + +} + +/** A list comprehension, such as `[ chr(x) for x in range(ord('A'), ord('Z')+1) ]` */ +class ListComp extends ListComp_, Comp { + + override Expr getASubExpression() { + result = this.getAGenerator().getASubExpression() or + result = this.getElt() or + result = this.getIterable() + } + + override AstNode getAChildNode() { + result = this.getAGenerator() or + result = this.getIterable() or + result = this.getFunction() + } + + override predicate hasSideEffects() { + any() + } + + /** Gets the scope in which the body of this list comprehension evaluates. */ + override Scope getEvaluatingScope() { + major_version() = 2 and result = this.getScope() + or + major_version() = 3 and result = this.getFunction() + } + + /** Gets the iteration variable for the nth innermost generator of this list comprehension */ + override Variable getIterationVariable(int n) { + result = Comp.super.getIterationVariable(n) + } + + override Function getFunction() { + result = ListComp_.super.getFunction() + } + + override string toString() { + result = ListComp_.super.toString() + } + + override Expr getElt() { + result = Comp.super.getElt() + } + +} + + +/** A set comprehension such as `{ v for v in "0123456789" }` */ +class SetComp extends SetComp_, Comp { + + override Expr getASubExpression() { + result = this.getIterable() + } + + override AstNode getAChildNode() { + result = this.getASubExpression() or + result = this.getFunction() + } + + override predicate hasSideEffects() { + any() + } + + override Function getFunction() { + result = SetComp_.super.getFunction() + } + +} + +/** A dictionary comprehension, such as `{ k:v for k, v in enumerate("0123456789") }` */ +class DictComp extends DictComp_, Comp { + + override Expr getASubExpression() { + result = this.getIterable() + } + + override AstNode getAChildNode() { + result = this.getASubExpression() or + result = this.getFunction() + } + + override predicate hasSideEffects() { + any() + } + + override Function getFunction() { + result = DictComp_.super.getFunction() + } + +} + + +/** A generator expression, such as `(var for var in iterable)` */ +class GeneratorExp extends GeneratorExp_, Comp { + + override Expr getASubExpression() { + result = this.getIterable() + } + + override AstNode getAChildNode() { + result = this.getASubExpression() or + result = this.getFunction() + } + + override predicate hasSideEffects() { + any() + } + + override Function getFunction() { + result = GeneratorExp_.super.getFunction() + } + +} + diff --git a/python/ql/src/semmle/python/Constants.qll b/python/ql/src/semmle/python/Constants.qll new file mode 100644 index 000000000000..c95f6bf9dc5f --- /dev/null +++ b/python/ql/src/semmle/python/Constants.qll @@ -0,0 +1,39 @@ +/** Standard builtin types and modules */ + +import python + +/** the Python major version number */ +int major_version() { + explicit_major_version(result) + or + not explicit_major_version(_) and + /* If there is more than one version, prefer 2 for backwards compatibilty */ + ( + if py_flags_versioned("version.major", "2", "2") then + result = 2 + else + result = 3 + ) +} + +/** the Python minor version number */ +int minor_version() { + exists(string v | py_flags_versioned("version.minor", v, major_version().toString()) | + result = v.toInt()) + +} + +/** the Python micro version number */ +int micro_version() { + exists(string v | py_flags_versioned("version.micro", v, major_version().toString()) | + result = v.toInt()) +} + +private predicate explicit_major_version(int v) { + exists(string version | + py_flags_versioned("language.version", version, _) | + version.charAt(0) = "2" and v = 2 + or + version.charAt(0) = "3" and v = 3 + ) +} diff --git a/python/ql/src/semmle/python/Exprs.qll b/python/ql/src/semmle/python/Exprs.qll new file mode 100644 index 000000000000..30f34b0a5499 --- /dev/null +++ b/python/ql/src/semmle/python/Exprs.qll @@ -0,0 +1,854 @@ +import python +private import semmle.python.pointsto.PointsTo + +/** An expression */ +class Expr extends Expr_, AstNode { + + /** Gets the scope of this expression */ + override Scope getScope() { + py_scopes(this, result) + } + + override string toString() { + result = "Expression" + } + + /** Gets the module in which this expression occurs */ + Module getEnclosingModule() { + result = this.getScope().getEnclosingModule() + } + + /** Whether this expression defines variable `v` + * If doing dataflow, then consider using SsaVariable.getDefinition() for more precision. */ + predicate defines(Variable v) { + this.getASubExpression+().defines(v) + } + + /** 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 */ + this.getAFlowNode().getASuccessor().getNode() instanceof ExceptStmt + or + this.getASubExpression().hasSideEffects() + } + + /** Whether this expression is a constant */ + predicate isConstant() { + not this.isVariable() + } + + /** Use isParenthesized instead. */ + override deprecated predicate isParenthesised() { + this.isParenthesized() + } + + /** Whether the parenthesized property of this expression is true. */ + predicate isParenthesized() { + Expr_.super.isParenthesised() + } + + private predicate isVariable() { + this.hasSideEffects() or + this instanceof Name or + exists(Expr e | e = this.getASubExpression() and e.isVariable()) + } + + override Location getLocation() { + result = Expr_.super.getLocation() + } + + /** Gets an immediate (non-nested) sub-expression of this expression */ + Expr getASubExpression() { + none() + } + + /** Use StrConst.getText() instead */ + deprecated string strValue() { + none() + } + + override AstNode getAChildNode() { + result = this.getASubExpression() + } + + /** Gets what this expression might "refer-to". Performs a combination of localized (intra-procedural) points-to + * analysis and global module-level analysis. This points-to analysis favours precision over recall. It is highly + * precise, but may not provide information for a significant number of flow-nodes. + * If the class is unimportant then use `refersTo(value)` or `refersTo(value, origin)` instead. + * NOTE: For complex dataflow, involving multiple stages of points-to analysis, it may be more precise to use + * `ControlFlowNode.refersTo(...)` instead. + */ + predicate refersTo(Object value, ClassObject cls, AstNode origin) { + not py_special_objects(cls, "_semmle_unknown_type") + and + not value = unknownValue() + and + PointsTo::points_to(this.getAFlowNode(), _, value, cls, origin.getAFlowNode()) + } + + /** Gets what this expression might "refer-to" in the given `context`. + */ + predicate refersTo(Context context, Object value, ClassObject cls, AstNode origin) { + not py_special_objects(cls, "_semmle_unknown_type") + and + PointsTo::points_to(this.getAFlowNode(), context, value, cls, origin.getAFlowNode()) + } + + /** Whether this expression might "refer-to" to `value` which is from `origin` + * Unlike `this.refersTo(value, _, origin)`, this predicate includes results + * where the class cannot be inferred. + */ + predicate refersTo(Object value, AstNode origin) { + PointsTo::points_to(this.getAFlowNode(), _, value, _, origin.getAFlowNode()) + and + not value = unknownValue() + } + + /** Equivalent to `this.refersTo(value, _)` */ + predicate refersTo(Object value) { + PointsTo::points_to(this.getAFlowNode(), _, value, _, _) + and + not value = unknownValue() + } + +} + +/** An attribute expression, such as `value.attr` */ +class Attribute extends Attribute_ { + + 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() + } + + /** Gets the object of this attribute. That is the `obj` in `obj.name` */ + Expr getObject() { + result = Attribute_.super.getValue() + } + + /** Gets the expression corresponding to the object of the attribute, if the name of the attribute is `name`. + * Equivalent to `this.getObject() and this.getName() = name`. */ + Expr getObject(string name) { + result = Attribute_.super.getValue() and + name = Attribute_.super.getAttr() + } + +} + +/** A subscript expression, such as `value[slice]` */ +class Subscript extends Subscript_ { + + override Expr getASubExpression() { + result = this.getIndex() + or + result = this.getObject() + } + + Expr getObject() { + result = Subscript_.super.getValue() + } + + override SubscriptNode getAFlowNode() { result = super.getAFlowNode() } +} + +/** A call expression, such as `func(...)` */ +class Call extends Call_ { + + override Expr getASubExpression() { + result = this.getAPositionalArg() or + result = this.getAKeyword().getValue() or + result = this.getFunc() + } + + override predicate hasSideEffects() { + any() + } + + override string toString() { + result = this.getFunc().toString() + "()" + } + + override CallNode getAFlowNode() { result = super.getAFlowNode() } + + /** Gets a tuple (*) argument of this class definition. */ + Expr getStarargs() { + result = this.getAPositionalArg().(Starred).getValue() + } + + /** Gets a dictionary (**) argument of this class definition. */ + Expr getKwargs() { + result = this.getANamedArg().(DictUnpacking).getValue() + } + + /* Backwards compatibility */ + + /** Gets the nth keyword argument of this call expression, provided it is not preceded by a double-starred argument. + * This exists primarily for backwards compatibility. You are recommended to use + * Call.getNamedArg(index) instead. + * */ + Keyword getKeyword(int index) { + result = this.getNamedArg(index) and not exists(DictUnpacking d, int lower | d = this.getNamedArg(lower) and lower < index) + } + + /** Gets a keyword argument of this call expression, provided it is not preceded by a double-starred argument. + * This exists primarily for backwards compatibility. You are recommended to use + * Call.getANamedArg() instead. + * */ + Keyword getAKeyword() { + result = this.getKeyword(_) + } + + /** Gets the positional argument at `index`, provided it is not preceded by a starred argument. + * This exists primarily for backwards compatibility. You are recommended to use + * Call.getPositionalArg(index) instead. + */ + Expr getArg(int index) { + result = this.getPositionalArg(index) and + not result instanceof Starred and + not exists(Starred s, int lower | s = this.getPositionalArg(lower) and lower < index) + } + + /** Gets a positional argument, provided it is not preceded by a starred argument. + * This exists primarily for backwards compatibility. You are recommended to use + * Call.getAPositionalArg() instead. + */ + Expr getAnArg() { + result = this.getArg(_) + } + + override AstNode getAChildNode() { + result = this.getAPositionalArg() or + result = this.getANamedArg() or + result = this.getFunc() + } + + /** Gets the name of a named argument, including those passed in dict literals. */ + string getANamedArgumentName() { + result = this.getAKeyword().getArg() + or + result = this.getKwargs().(Dict).getAKey().(StrConst).getText() + } + +} + +/** A conditional expression such as, `body if test else orelse` */ +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` */ +class Starred extends Starred_ { + + override Expr getASubExpression() { + result = this.getValue() + } + +} + + +/** A yield expression, such as `yield value` */ +class Yield extends Yield_ { + + override Expr getASubExpression() { + result = this.getValue() + } + + override predicate hasSideEffects() { + any() + } + +} + +/** A yield expression, such as `yield from value` */ +class YieldFrom extends YieldFrom_ { + + override Expr getASubExpression() { + result = this.getValue() + } + + override predicate hasSideEffects() { + any() + } + +} + +/** A repr (backticks) expression, such as `` `value` `` */ +class Repr extends Repr_ { + + override Expr getASubExpression() { + result = this.getValue() + } + + override predicate hasSideEffects() { + any() + } + +} + +/* Constants */ + +/** A bytes constant, such as `b'ascii'`. Note that unadorned string constants such as + `"hello"` are treated as Bytes for Python2, but Unicode for Python3. */ +class Bytes extends StrConst { + + Bytes() { + not this.isUnicode() + } + + override Object getLiteralObject() { + py_cobjecttypes(result, theBytesType()) and + py_cobjectnames(result, this.quotedString()) + } + + /** The extractor puts quotes into the name of each string (to prevent "0" clashing with 0). + * The following predicate help us match up a string/byte literals in the source + * which the equivalent object. + */ + private string quotedString() { + exists(string b_unquoted | + b_unquoted = this.getS() | + result = "b'" + b_unquoted + "'" + ) + } + +} + +/** An ellipsis expression, such as `...` */ +class Ellipsis extends Ellipsis_ { + + override Expr getASubExpression() { + none() + } + +} + +/** Immutable literal expressions (except tuples). + * Consists of string (both unicode and byte) literals + * and numeric literals. + */ +abstract class ImmutableLiteral extends Expr { + + abstract Object getLiteralObject(); + + abstract boolean booleanValue(); + +} + +/** A numerical constant expression, such as `7` or `4.2` */ +abstract class Num extends Num_, ImmutableLiteral { + + override Expr getASubExpression() { + none() + } + + /* We want to declare this abstract, but currently we cannot. */ + override string toString() { + none() + } + +} + +/** An integer numeric constant, such as `7` or `0x9` */ +class IntegerLiteral extends Num { + + IntegerLiteral() { + not this instanceof FloatLiteral and not this instanceof ImaginaryLiteral + } + + /** Gets the (integer) value of this constant. Will not return a result if the value does not fit into + a 32 bit signed value */ + int getValue() { + result = this.getN().toInt() + } + + override string toString() { + result = "IntegerLiteral" + } + + override Object getLiteralObject() { + py_cobjecttypes(result, theIntType()) and py_cobjectnames(result, this.getN()) + or + py_cobjecttypes(result, theLongType()) and py_cobjectnames(result, this.getN()) + } + + override boolean booleanValue() { + this.getValue() = 0 and result = false + or + this.getValue() != 0 and result = true + } + +} + +/** A floating point numeric constant, such as `0.4` or `4e3` */ +class FloatLiteral extends Num { + + FloatLiteral() { + not this instanceof ImaginaryLiteral and + exists(string n | n = this.getN() | n.charAt(_) = "." or n.charAt(_) = "e" or n.charAt(_) = "E") + } + + float getValue() { + result = this.getN().toFloat() + } + + override string toString() { + result = "FloatLiteral" + } + + override Object getLiteralObject() { + py_cobjecttypes(result, theFloatType()) and py_cobjectnames(result, this.getN()) + } + + override boolean booleanValue() { + this.getValue() = 0.0 and result = false + or + // In QL 0.0 != -0.0 + this.getValue() = -0.0 and result = false + or + this.getValue() != 0.0 and this.getValue() != -0.0 and result = true + } + +} + +/** An imaginary numeric constant, such as `3j` */ +class ImaginaryLiteral extends Num { + + ImaginaryLiteral() { + exists(string n | n = this.getN() | n.charAt(_) = "j") + } + + /** Gets the value of this constant as a floating point value */ + float getValue() { + exists(string s, int j | s = this.getN() and s.charAt(j) = "j" | + result = s.prefix(j).toFloat()) + } + + override string toString() { + result = "ImaginaryLiteral" + } + + override Object getLiteralObject() { + py_cobjecttypes(result, theComplexType()) and py_cobjectnames(result, this.getN()) + } + + override boolean booleanValue() { + this.getValue() = 0.0 and result = false + or + // In QL 0.0 != -0.0 + this.getValue() = -0.0 and result = false + or + this.getValue() != 0.0 and this.getValue() != -0.0 and result = true + } + +} + +/** A unicode string expression, such as `u"\u20ac"`. Note that unadorned string constants such as + "hello" are treated as Bytes for Python2, but Unicode for Python3. */ +class Unicode extends StrConst { + + Unicode() { + this.isUnicode() + } + + override Object getLiteralObject() { + py_cobjecttypes(result, theUnicodeType()) and + py_cobjectnames(result, this.quotedString()) + } + + /** The extractor puts quotes into the name of each string (to prevent "0" clashing with 0). + * The following predicate help us match up a string/byte literals in the source + * which the equivalent object. + */ + string quotedString() { + exists(string u_unquoted | + u_unquoted = this.getS() | + result = "u'" + u_unquoted + "'" + ) + } + +} + + +/* Compound Values */ + +/** A dictionary expression, such as `{'key':'value'}` */ +class Dict extends Dict_ { + + /** Gets the value of an item of this dict display */ + Expr getAValue() { + result = this.getAnItem().(DictDisplayItem).getValue() + } + + /** Gets the key of an item of this dict display, for those items that have keys + * E.g, in {'a':1, **b} this returns only 'a' + */ + Expr getAKey() { + result = this.getAnItem().(KeyValuePair).getKey() + } + + override Expr getASubExpression() { + result = this.getAValue() or result = this.getAKey() + } + + AstNode getAChildNode() { + result = this.getAnItem() + } + +} + +/** A list expression, such as `[ 1, 3, 5, 7, 9 ]` */ +class List extends List_ { + + override Expr getASubExpression() { + result = this.getAnElt() + } + +} + +/** A set expression such as `{ 1, 3, 5, 7, 9 }` */ +class Set extends Set_ { + + override Expr getASubExpression() { + result = this.getAnElt() + } + +} + +class PlaceHolder extends PlaceHolder_ { + + string getId() { + result = this.getVariable().getId() + } + + 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 )` */ +class Tuple extends Tuple_ { + + override Expr getASubExpression() { + result = this.getAnElt() + } + +} + +/** A (plain variable) name expression, such as `var`. + * `None`, `True` and `False` are excluded. + */ +class Name extends Name_ { + + string getId() { + result = this.getVariable().getId() + } + + /** Whether this expression is a definition */ + predicate isDefinition() { + py_expr_contexts(_, 5, this) or + /* Treat Param as a definition (which it is) */ + py_expr_contexts(_, 4, this) or + /* The target in an augmented assignment is also a definition (and a use) */ + exists(AugAssign aa | aa.getTarget() = this) + } + + /** Whether this expression defines variable `v` + * If doing dataflow, then consider using SsaVariable.getDefinition() for more precision. */ + override predicate defines(Variable v) { + this.isDefinition() + and + v = this.getVariable() + } + + /** Whether this expression is a definition */ + predicate isDeletion() { + py_expr_contexts(_, 2, this) + } + + /** Whether this expression deletes variable `v`. + * If doing dataflow, then consider using SsaVariable.getDefinition() for more precision. */ + predicate deletes(Variable v) { + this.isDeletion() + and + v = this.getVariable() + } + + /** Whether this expression is a use */ + predicate isUse() { + py_expr_contexts(_, 3, this) + } + + /** Whether this expression is a use of variable `v` + * If doing dataflow, then consider using SsaVariable.getAUse() for more precision. */ + predicate uses(Variable v) { + this.isUse() + and + v = this.getVariable() + } + + override predicate isConstant() { + none() + } + + override Expr getASubExpression() { + none() + } + + 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) = "." + } + +} + +class Filter extends Filter_ { + + override Expr getASubExpression() { + result = this.getFilter() + or + result = this.getValue() + } + +} + + +/** A slice. E.g `0:1` in the expression `x[0:1]` */ +class Slice extends Slice_ { + + override Expr getASubExpression() { + result = this.getStart() or + result = this.getStop() or + result = this.getStep() + } + +} + +/** A string constant. */ +class StrConst extends Str_, ImmutableLiteral { + + predicate isUnicode() { + this.getPrefix().charAt(_) = "u" + or + this.getPrefix().charAt(_) = "U" + or + not this.getPrefix().charAt(_) = "b" and major_version() = 3 + or + not this.getPrefix().charAt(_) = "b" and this.getEnclosingModule().hasFromFuture("unicode_literals") + } + + override + string strValue() { + result = this.getS() + } + + override Expr getASubExpression() { + none() + } + + override AstNode getAChildNode() { + result = this.getAnImplicitlyConcatenatedPart() + } + + /** Gets the text of this str constant */ + string getText() { + result = this.getS() + } + + /** Whether this is a docstring */ + predicate isDocString() { + exists(Scope s | s.getDocString() = this) + } + + override boolean booleanValue() { + this.getText() = "" and result = false + or + this.getText() != "" and result = true + } + + override Object getLiteralObject() { none() } + +} + +private predicate name_consts(Name_ n, string id) { + exists(Variable v | + py_variables(v, n) and id = v.getId() | + id = "True" or id = "False" or id = "None" + ) +} + +/** A named constant, one of `None`, `True` or `False` */ +abstract class NameConstant extends Name, ImmutableLiteral { + + NameConstant() { + name_consts(this, _) + } + + override Expr getASubExpression() { + none() + } + + override string toString() { + name_consts(this, result) + } + + override predicate isConstant() { + any() + } + + override NameConstantNode getAFlowNode() { result = Name.super.getAFlowNode() } + + override predicate isArtificial() { + none() + } + +} + +/** A boolean named constant, either `True` or `False` */ +abstract class BooleanLiteral extends NameConstant { + +} + +/** The boolean named constant `True` */ +class True extends BooleanLiteral { + + True() { + name_consts(this, "True") + } + + override Object getLiteralObject() { + name_consts(this, "True") and result = theTrueObject() + } + + override boolean booleanValue() { + result = true + } + +} + +/** The boolean named constant `False` */ +class False extends BooleanLiteral { + + False() { + name_consts(this, "False") + } + + override Object getLiteralObject() { + name_consts(this, "False") and result = theFalseObject() + } + + override boolean booleanValue() { + result = false + } + +} + +/** `None` */ +class None extends NameConstant { + + None() { + name_consts(this, "None") + } + + override Object getLiteralObject() { + name_consts(this, "None") and result = theNoneObject() + } + + override boolean booleanValue() { + result = false + } + +} + +/** An await expression such as `await coro`. */ +class Await extends Await_ { + + override Expr getASubExpression() { + result = this.getValue() + } + +} + +/** A formatted string literal expression, such as `f'hello {world!s}'` */ +class Fstring extends Fstring_ { + + override Expr getASubExpression() { + result = this.getAValue() + } + +} + +/** A formatted value (within a formatted string literal). + * For example, in the string `f'hello {world!s}'` the formatted value is `world!s`. + */ +class FormattedValue extends FormattedValue_ { + + override Expr getASubExpression() { + result = this.getValue() or + result = this.getFormatSpec() + } + + +} + +/* Expression Contexts */ + +/** A context in which an expression used */ +class ExprContext extends ExprContext_ { + +} + +/** Load context, the context of var in len(var) */ +class Load extends Load_ { + +} + +/** Store context, the context of var in var = 0 */ +class Store extends Store_ { + +} + +/** Delete context, the context of var in del var */ +class Del extends Del_ { + +} + +/** This is an artifact of the Python grammar which includes an AugLoad context, even though it is never used. */ +library class AugLoad extends AugLoad_ { + +} + +/** Augmented store context, the context of var in var += 1 */ +class AugStore extends AugStore_ { + +} + +/** Parameter context, the context of var in def f(var): pass */ +class Param extends Param_ { + +} + + diff --git a/python/ql/src/semmle/python/Files.qll b/python/ql/src/semmle/python/Files.qll new file mode 100644 index 000000000000..c743b0373c52 --- /dev/null +++ b/python/ql/src/semmle/python/Files.qll @@ -0,0 +1,500 @@ + +import python + +/** A file */ +class File extends Container { + + File() { + files(this, _, _, _, _) + } + + /** DEPRECATED: Use `getAbsolutePath` instead. */ + override string getName() { + files(this, result, _, _, _) + } + + /** DEPRECATED: Use `getAbsolutePath` instead. */ + string getFullName() { + result = getName() + } + + predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { + this.getName() = filepath and bl = 0 and bc = 0 and el = 0 and ec = 0 + } + + /** Whether this file is a source code file. */ + predicate fromSource() { + /* If we start to analyse .pyc files, then this will have to change. */ + any() + } + + /** Gets a short name for this file (just the file name) */ + string getShortName() { + exists(string simple, string ext | files(this, _, simple, ext, _) | + result = simple + ext) + } + + private int lastLine() { + result = max(int i | exists(Location l | l.getFile() = this and l.getEndLine() = i)) + } + + /** Whether line n is empty (it contains neither code nor comment). */ + predicate emptyLine(int n) { + n in [0..this.lastLine()] + and + not occupied_line(this, n) + } + + string getSpecifiedEncoding() { + exists(Comment c, Location l | + l = c.getLocation() and l.getFile() = this | + l.getStartLine() < 3 and + result = c.getText().regexpCapture(".*coding[:=]\\s*([-\\w.]+).*", 1) + ) + } + + override string getAbsolutePath() { + files(this, result, _, _, _) + } + + /** Gets the URL of this file. */ + override string getURL() { + result = "file://" + this.getAbsolutePath() + ":0:0:0:0" + } + + override Container getImportRoot(int n) { + /* File stem must be a legal Python identifier */ + this.getStem().regexpMatch("[^\\d\\W]\\w*") and + result = this.getParent().getImportRoot(n) + } + + /** Gets the contents of this file as a string. + * This will only work for those non-python files that + * are specified to be extracted. + */ + string getContents() { + file_contents(this, result) + } + +} + +private predicate occupied_line(File f, int n) { + exists(Location l | + l.getFile() = f | + l.getStartLine() = n + or + exists(StrConst s | s.getLocation() = l | + n in [l.getStartLine() .. l.getEndLine()] + ) + ) +} + +/** A folder (directory) */ +class Folder extends Container { + + Folder() { + folders(this, _, _) + } + + /** DEPRECATED: Use `getAbsolutePath` instead. */ + override string getName() { + folders(this, result, _) + } + + /** DEPRECATED: Use `getBaseName` instead. */ + string getSimple() { + folders(this, _, result) + } + + predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { + this.getName() = filepath and bl = 0 and bc = 0 and el = 0 and ec = 0 + } + + override string getAbsolutePath() { + folders(this, result, _) + } + + /** Gets the URL of this folder. */ + override string getURL() { + result = "folder://" + this.getAbsolutePath() + } + + override Container getImportRoot(int n) { + this.isImportRoot(n) and result = this + or + /* Folder must be a legal Python identifier */ + this.getBaseName().regexpMatch("[^\\d\\W]\\w*") and + result = this.getParent().getImportRoot(n) + } + +} + +/** A container is an abstract representation of a file system object that can + hold elements of interest. */ +abstract class Container extends @container { + + Container getParent() { + containerparent(result, this) + } + + /** Gets a child of this container */ + deprecated Container getChild() { + containerparent(this, result) + } + + /** + * Gets a textual representation of the path of this container. + * + * This is the absolute path of the container. + */ + string toString() { + result = this.getAbsolutePath() + } + + /** Gets the name of this container */ + abstract string getName(); + + /** + * Gets the relative path of this file or folder from the root folder of the + * analyzed source location. The relative path of the root folder itself is + * the empty string. + * + * This has no result if the container is outside the source root, that is, + * if the root folder is not a reflexive, transitive parent of this container. + */ + string getRelativePath() { + exists (string absPath, string pref | + absPath = this.getAbsolutePath() and sourceLocationPrefix(pref) | + absPath = pref and result = "" + or + absPath = pref.regexpReplaceAll("/$", "") + "/" + result and + not result.matches("/%") + ) + } + + /** Whether this file or folder is part of the standard library */ + predicate inStdlib() { + this.inStdlib(_, _) + } + + /** Whether this file or folder is part of the standard library + * for version `major.minor` + */ + predicate inStdlib(int major, int minor) { + exists(Module m | + m.getPath() = this and + m.inStdLib(major, minor) + ) + } + + /* Standard cross-language API */ + + /** Gets a file or sub-folder in this container. */ + Container getAChildContainer() { + containerparent(this, result) + } + + /** Gets a file in this container. */ + File getAFile() { + result = this.getAChildContainer() + } + + /** Gets a sub-folder in this container. */ + Folder getAFolder() { + result = this.getAChildContainer() + } + + /** + * Gets the absolute, canonical path of this container, using forward slashes + * as path separator. + * + * The path starts with a _root prefix_ followed by zero or more _path + * segments_ separated by forward slashes. + * + * The root prefix is of one of the following forms: + * + * 1. A single forward slash `/` (Unix-style) + * 2. An upper-case drive letter followed by a colon and a forward slash, + * such as `C:/` (Windows-style) + * 3. Two forward slashes, a computer name, and then another forward slash, + * such as `//FileServer/` (UNC-style) + * + * Path segments are never empty (that is, absolute paths never contain two + * contiguous slashes, except as part of a UNC-style root prefix). Also, path + * segments never contain forward slashes, and no path segment is of the + * form `.` (one dot) or `..` (two dots). + * + * Note that an absolute path never ends with a forward slash, except if it is + * a bare root prefix, that is, the path has no path segments. A container + * whose absolute path has no segments is always a `Folder`, not a `File`. + */ + abstract string getAbsolutePath(); + + /** + * Gets the base name of this container including extension, that is, the last + * segment of its absolute path, or the empty string if it has no segments. + * + * Here are some examples of absolute paths and the corresponding base names + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + * + *
    Absolute pathBase name
    "/tmp/tst.py""tst.py"
    "C:/Program Files (x86)""Program Files (x86)"
    "/"""
    "C:/"""
    "D:/"""
    "//FileServer/"""
    + */ + string getBaseName() { + result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1) + } + + /** + * Gets the extension of this container, that is, the suffix of its base name + * after the last dot character, if any. + * + * In particular, + * + * - if the name does not include a dot, there is no extension, so this + * predicate has no result; + * - if the name ends in a dot, the extension is the empty string; + * - if the name contains multiple dots, the extension follows the last dot. + * + * Here are some examples of absolute paths and the corresponding extensions + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + *
    Absolute pathExtension
    "/tmp/tst.py""py"
    "/tmp/.gitignore""gitignore"
    "/bin/bash"not defined
    "/tmp/tst2."""
    "/tmp/x.tar.gz""gz"
    + */ + string getExtension() { + result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) + } + + /** + * Gets the stem of this container, that is, the prefix of its base name up to + * (but not including) the last dot character if there is one, or the entire + * base name if there is not. + * + * Here are some examples of absolute paths and the corresponding stems + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + *
    Absolute pathStem
    "/tmp/tst.py""tst"
    "/tmp/.gitignore"""
    "/bin/bash""bash"
    "/tmp/tst2.""tst2"
    "/tmp/x.tar.gz""x.tar"
    + */ + string getStem() { + result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) + } + + File getFile(string baseName) { + result = this.getAFile() and + result.getBaseName() = baseName + } + + Folder getFolder(string baseName) { + result = this.getAFolder() and + result.getBaseName() = baseName + } + + Container getParentContainer() { + this = result.getAChildContainer() + } + + Container getChildContainer(string baseName) { + result = this.getAChildContainer() and + result.getBaseName() = baseName + } + + /** + * Gets a URL representing the location of this container. + * + * For more information see https://lgtm.com/help/ql/locations#providing-urls. + */ + abstract string getURL(); + + /** Holds if this folder is on the import path. */ + predicate isImportRoot() { + this.isImportRoot(_) + } + + /** Holds if this folder is on the import path, at index `n` in the list of + * paths. The list of paths is composed of the paths passed to the extractor and + * `sys.path`. */ + predicate isImportRoot(int n) { + this.getName() = import_path_element(n) + } + + /** Holds if this folder is the root folder for the standard library. */ + predicate isStdLibRoot(int major, int minor) { + allowable_version(major, minor) and + this.isImportRoot() and + this.getBaseName().regexpMatch("python" + major + "." + minor) + } + + /** Gets the path element from which this container would be loaded. */ + Container getImportRoot() { + exists(int n | + result = this.getImportRoot(n) and + not exists(int m | + exists(this.getImportRoot(m)) and + m < n + ) + ) + } + + /** Gets the path element from which this container would be loaded, given the index into the list of possible paths `n`. */ + abstract Container getImportRoot(int n); + +} + +private string import_path_element(int n) { + exists(string path, string pathsep, int k | + path = get_path("extractor.path") and k = 0 + or + path = get_path("sys.path") and k = count(get_path("extractor.path").splitAt(pathsep)) + | + py_flags_versioned("os.pathsep", pathsep, _) and + result = path.splitAt(pathsep, n-k).replaceAll("\\", "/") + ) +} + +private string get_path(string name) { + py_flags_versioned(name, result, _) +} + +private predicate allowable_version(int major, int minor) { + major = 2 and minor in [6..7] + or + major = 3 and minor in [3..6] +} + +class Location extends @location { + + /** Gets the file for this location */ + File getFile() { + locations_default(this, result, _, _, _, _) + or + exists(Module m | locations_ast(this, m, _, _, _, _) | + result = m.getFile() + ) + } + + /** Gets the start line of this location */ + int getStartLine() { + locations_default(this, _, result, _, _, _) + or locations_ast(this,_,result,_,_,_) + } + + /** Gets the start column of this location */ + int getStartColumn() { + locations_default(this, _, _, result, _, _) + or locations_ast(this, _, _, result, _, _) + } + + /** Gets the end line of this location */ + int getEndLine() { + locations_default(this, _, _, _, result, _) + or locations_ast(this, _, _, _, result, _) + } + + /** Gets the end column of this location */ + int getEndColumn() { + locations_default(this, _, _, _, _, result) + or locations_ast(this, _, _, _, _, result) + } + + string toString() { + result = this.getFile().getName() + ":" + this.getStartLine().toString() + } + + predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { + exists(File f | f.getName() = filepath | + locations_default(this, f, bl, bc, el, ec) + or + exists(Module m | m.getFile() = f | + locations_ast(this, m, bl, bc, el, ec)) + ) + } + +} + +/** A non-empty line in the source code */ +class Line extends @py_line { + + predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { + exists(Module m | m.getFile().getName() = filepath and + el = bl and bc = 1 and + py_line_lengths(this, m, bl, ec)) + } + + string toString() { + exists(Module m | py_line_lengths(this, m, _, _) | + result = m.getFile().getShortName() + ":" + this.getLineNumber().toString() + ) + } + + /** Gets the line number of this line */ + int getLineNumber() { + py_line_lengths(this, _, result, _) + } + + /** Gets the length of this line */ + int getLength() { + py_line_lengths(this, _, _, result) + } + + /** Gets the file for this line */ + Module getModule() { + py_line_lengths(this, result, _, _) + } + +} + +/** A syntax error. Note that if there is a syntax error in a module, + much information about that module will be lost */ +class SyntaxError extends Location { + + SyntaxError() { + py_syntax_error_versioned(this, _, major_version().toString()) + } + + override string toString() { + result = "Syntax Error" + } + + /** Gets the message corresponding to this syntax error */ + string getMessage() { + py_syntax_error_versioned(this, result, major_version().toString()) + } + +} + +/** An encoding error. Note that if there is an encoding error in a module, + much information about that module will be lost */ +class EncodingError extends SyntaxError { + + EncodingError() { + /* Leave spaces around 'decode' in unlikely event it occurs as a name in a syntax error */ + this.getMessage().toLowerCase().matches("% decode %") + } + + override string toString() { + result = "Encoding Error" + } + +} + + diff --git a/python/ql/src/semmle/python/Flow.qll b/python/ql/src/semmle/python/Flow.qll new file mode 100755 index 000000000000..a465d88533ab --- /dev/null +++ b/python/ql/src/semmle/python/Flow.qll @@ -0,0 +1,1047 @@ +import python +import semmle.python.flow.NameNode +private import semmle.python.pointsto.PointsTo + + +/* Note about matching parent and child nodes and CFG splitting: + * + * As a result of CFG splitting a single AST node may have multiple CFG nodes. + * Therefore, when matching CFG nodes to children, we need to make sure that + * we don't match the child of one CFG node to the wrong parent. + * We do this by checking dominance. If the CFG node for the parent precedes that of + * the child, then he child node matches the parent node if it is dominated by it. + * Vice versa for child nodes that precede the parent. + */ + + +private predicate augstore(ControlFlowNode load, ControlFlowNode 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) + ) +} + +/** A non-dispatched getNode() to avoid negative recursion issues */ +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, + * although most syntactic nodes have only one corresponding control flow node. +* Edges between control flow nodes include exceptional as well as normal control flow. +*/ +class ControlFlowNode extends @py_flow_node { + + /** Whether this control flow node is a load (including those in augmented assignments) */ + predicate isLoad() { + 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(Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this)) + } + + /** Whether this control flow node is a delete */ + predicate isDelete() { + exists(Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) + } + + /** Whether this control flow node is a parameter */ + 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) + } + + /** Whether this control flow node is a load in an augmented assignment */ + predicate isAugLoad() { + augstore(this, _) + } + + /** Whether this flow node corresponds to a literal */ + predicate isLiteral() { + toAst(this) instanceof Bytes + or + toAst(this) instanceof Dict + or + toAst(this) instanceof DictComp + or + toAst(this) instanceof Set + or + toAst(this) instanceof SetComp + or + toAst(this) instanceof Ellipsis + or + toAst(this) instanceof GeneratorExp + or + toAst(this) instanceof Lambda + or + toAst(this) instanceof ListComp + or + toAst(this) instanceof List + or + toAst(this) instanceof Num + or + toAst(this) instanceof Tuple + or + toAst(this) instanceof Unicode + or + toAst(this) instanceof NameConstant + } + + /** Use NameNode.isLoad() instead */ + deprecated predicate isUse() { + toAst(this) instanceof Name and this.isLoad() + } + + /** Use NameNode.isStore() */ + deprecated predicate isDefinition() { + toAst(this) instanceof Name and this.isStore() + } + + /** Whether this flow node corresponds to an attribute expression */ + predicate isAttribute() { + toAst(this) instanceof Attribute + } + + /** Use AttrNode.isLoad() instead */ + deprecated predicate isAttributeLoad() { + toAst(this) instanceof Attribute and this.isLoad() + } + + /** Use AttrNode.isStore() instead */ + deprecated predicate isAttributeStore() { + toAst(this) instanceof Attribute and this.isStore() + } + + /** Whether this flow node corresponds to an subscript expression */ + predicate isSubscript() { + toAst(this) instanceof Subscript + } + + /** Use SubscriptNode.isLoad() instead */ + deprecated predicate isSubscriptLoad() { + toAst(this) instanceof Subscript and this.isLoad() + } + + /** Use SubscriptNode.isStore() instead */ + deprecated predicate isSubscriptStore() { + toAst(this) instanceof Subscript and this.isStore() + } + + /** Whether this flow node corresponds to an import member */ + predicate isImportMember() { + toAst(this) instanceof ImportMember + } + + /** Whether this flow node corresponds to a 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 Module + } + + /** Whether this flow node corresponds to an import */ + predicate isImport() { + toAst(this) instanceof ImportExpr + } + + /** Whether this flow node corresponds to a conditional expression */ + predicate isIfExp() { + toAst(this) instanceof IfExp + } + + /** Whether this flow node corresponds to a function definition expression */ + predicate isFunction() { + toAst(this) instanceof FunctionExpr + } + + /** Whether this flow node corresponds to a class definition expression */ + predicate isClass() { + toAst(this) instanceof ClassExpr + } + + /** Gets a predecessor of this flow node */ + ControlFlowNode getAPredecessor() { + py_successors(result, this) + } + + /** Gets a successor of this flow node */ + ControlFlowNode getASuccessor() { + py_successors(this, result) + } + + /** Gets the immediate dominator of this flow node */ + ControlFlowNode getImmediateDominator() { + py_idoms(this, result) + } + + /** Gets the syntactic element corresponding to this flow node */ + AstNode getNode() { + py_flow_bb_node(this, result, _, _) + } + + string toString() { + exists(Scope s | s.getEntryNode() = this | + result = "Entry node for " + s.toString() + ) + or + exists(Scope s | s.getANormalExit() = this | + result = "Exit node for " + s.toString() + ) + or + not exists(Scope s | s.getEntryNode() = this or s.getANormalExit() = this) and + result = "ControlFlowNode for " + this.getNode().toString() + } + + /** Gets the location of this ControlFlowNode */ + Location getLocation() { + result = this.getNode().getLocation() + } + + /** Whether this flow node is the first in its scope */ + predicate isEntryNode() { + py_scope_flow(this, _, -1) + } + + /** Use ControlFlowNode.refersTo() instead. */ + deprecated Object pointsTo() { + this.refersTo(result) + } + + /** Gets what this flow node might "refer-to". Performs a combination of localized (intra-procedural) points-to + * analysis and global module-level analysis. This points-to analysis favours precision over recall. It is highly + * precise, but may not provide information for a significant number of flow-nodes. + * If the class is unimportant then use `refersTo(value)` or `refersTo(value, origin)` instead. + */ + predicate refersTo(Object value, ClassObject cls, ControlFlowNode origin) { + not py_special_objects(cls, "_semmle_unknown_type") + and + not value = unknownValue() + and + PointsTo::points_to(this, _, value, cls, origin) + } + + /** Gets what this expression might "refer-to" in the given `context`. + */ + predicate refersTo(Context context, Object value, ClassObject cls, ControlFlowNode origin) { + not py_special_objects(cls, "_semmle_unknown_type") + and + PointsTo::points_to(this, context, value, cls, origin) + } + + /** Whether this flow node might "refer-to" to `value` which is from `origin` + * Unlike `this.refersTo(value, _, origin)` this predicate includes results + * where the class cannot be inferred. + */ + predicate refersTo(Object value, ControlFlowNode origin) { + PointsTo::points_to(this, _, value, _, origin) + and + not value = unknownValue() + } + + /** Equivalent to `this.refersTo(value, _)` */ + predicate refersTo(Object value) { + PointsTo::points_to(this, _, value, _, _) + and + not value = unknownValue() + } + + /** Gets the basic block containing this flow node */ + BasicBlock getBasicBlock() { + result.contains(this) + } + + /** Gets the scope containing this flow node */ + Scope getScope() { + if this.getNode() instanceof Scope then + /* Entry or exit node */ + result = this.getNode() + else + result = this.getNode().getScope() + } + + /** Gets the enclosing module */ + Module getEnclosingModule() { + result = this.getScope().getEnclosingModule() + } + + /** Gets a successor for this node if the relevant condition is True. */ + ControlFlowNode getATrueSuccessor() { + py_true_successors(this, result) + } + + /** Gets a successor for this node if the relevant condition is False. */ + ControlFlowNode getAFalseSuccessor() { + py_false_successors(this, result) + } + + /** Gets a successor for this node if an exception is raised. */ + ControlFlowNode getAnExceptionalSuccessor() { + py_exception_successors(this, result) + } + + /** Gets a successor for this node if no exception is raised. */ + ControlFlowNode getANormalSuccessor() { + py_successors(this, result) and not + py_exception_successors(this, result) + } + + /** Whether the scope may be exited as a result of this node raising an exception */ + 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) + } + + /** Whether it is unlikely that this ControlFlowNode can be reached */ + predicate unlikelyReachable() { + not start_bb_likely_reachable(this.getBasicBlock()) + or + exists(BasicBlock b | + start_bb_likely_reachable(b) and + not end_bb_likely_reachable(b) and + /* If there is an unlikely successor edge earlier in the BB + * than this node, then this node must be unreachable */ + exists(ControlFlowNode p, int i, int j | + p.(RaisingNode).unlikelySuccessor(_) and + p = b.getNode(i) and + this = b.getNode(j) and + i < j + ) + ) + } + + /** Check whether this control-flow node has complete points-to information. + * This would mean that the analysis managed to infer an over approximation + * of possible values at runtime. + */ + predicate hasCompletePointsToSet() { + ( + // If the tracking failed, then `this` will be its own "origin". In that + // case, we want to exclude nodes for which there is also a different + // origin, as that would indicate that some paths failed and some did not. + this.refersTo(_, _, this) and + not exists(ControlFlowNode other | other != this and this.refersTo(_, _, other)) + ) or ( + // If `this` is a use of a variable, then we must have complete points-to + // for that variable. + exists(SsaVariable v | v.getAUse() = this | + varHasCompletePointsToSet(v) + ) + ) + } + + /** Whether this strictly dominates other. */ + pragma [inline] predicate strictlyDominates(ControlFlowNode other) { + // This predicate is gigantic, so it must be inlined. + // About 1.4 billion tuples for OpenStack Cinder. + this.getBasicBlock().strictlyDominates(other.getBasicBlock()) + or + exists(BasicBlock b, int i, int j | + this = b.getNode(i) and other = b.getNode(j) and i < j + ) + } + + /** Whether this dominates other. + * Note that all nodes dominate themselves. + */ + pragma [inline] predicate dominates(ControlFlowNode other) { + // This predicate is gigantic, so it must be inlined. + this.getBasicBlock().strictlyDominates(other.getBasicBlock()) + or + exists(BasicBlock b, int i, int j | + this = b.getNode(i) and other = b.getNode(j) and i <= j + ) + } + + /** Whether this strictly reaches other. */ + pragma [inline] predicate strictlyReaches(ControlFlowNode other) { + // This predicate is gigantic, even larger than strictlyDominates, + // so it must be inlined. + this.getBasicBlock().strictlyReaches(other.getBasicBlock()) + or + exists(BasicBlock b, int i, int j | + this = b.getNode(i) and other = b.getNode(j) and i < j + ) + } + + /* Holds if this CFG node is a branch */ + predicate isBranch() { + py_true_successors(this, _) or py_false_successors(this, _) + } + + /* Gets a CFG node that corresponds to a child of the AST node for this node */ + pragma [noinline] + ControlFlowNode getAChild() { + this.getNode().getAChildNode() = result.getNode() and + result.getBasicBlock().dominates(this.getBasicBlock()) + } + +} + + +/* This class exists to provide an implementation over ControlFlowNode.getNode() + * that subsumes all the others in an way that's obvious to the optimiser. + * This avoids wasting time on the trivial overrides on the ControlFlowNode subclasses. + */ +private class AnyNode extends ControlFlowNode { + + override AstNode getNode() { + result = super.getNode() + } +} + + +/** Check whether a SSA variable has complete points-to information. + * This would mean that the analysis managed to infer an overapproximation + * of possible values at runtime. + */ +private predicate varHasCompletePointsToSet(SsaVariable var) { + // Global variables may be modified non-locally or concurrently. + not var.getVariable() instanceof GlobalVariable and + ( + // If we have complete points-to information on the definition of + // this variable, then the variable has complete information. + var.getDefinition().(DefinitionNode).getValue().hasCompletePointsToSet() + or + // If this variable is a phi output, then we have complete + // points-to information about it if all phi inputs had complete + // information. + forex(SsaVariable phiInput | phiInput = var.getAPhiInput() | + varHasCompletePointsToSet(phiInput) + ) + ) +} + +/** A control flow node corresponding to a call expression, such as `func(...)` */ +class CallNode extends ControlFlowNode { + + 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(Call c | this.getNode() = c and c.getFunc() = result.getNode() and + result.getBasicBlock().dominates(this.getBasicBlock())) + } + + /** Gets the flow node corresponding to the nth argument of the call corresponding to this flow node */ + ControlFlowNode getArg(int n) { + exists(Call c | this.getNode() = c and c.getArg(n) = result.getNode() and + result.getBasicBlock().dominates(this.getBasicBlock())) + } + + /** Gets the flow node corresponding to the named argument of the call corresponding to this flow node */ + ControlFlowNode getArgByName(string name) { + exists(Call c, Keyword k | this.getNode() = c and k = c.getAKeyword() and + k.getValue() = result.getNode() and k.getArg() = name and + result.getBasicBlock().dominates(this.getBasicBlock())) + } + + /** Gets the flow node corresponding to an argument of the call corresponding to this flow node */ + ControlFlowNode getAnArg() { + exists(int n | result = this.getArg(n)) + or + exists(string name | result = this.getArgByName(name)) + } + + override Call getNode() { result = super.getNode() } + +} + +/** A control flow corresponding to an attribute expression, such as `value.attr` */ +class AttrNode extends ControlFlowNode { + 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(Attribute a | this.getNode() = a and a.getObject() = result.getNode() and + result.getBasicBlock().dominates(this.getBasicBlock())) + } + + /** Use getObject() instead */ + deprecated ControlFlowNode getValue() { + result = this.getObject() + } + + /** Use getObject(name) instead */ + deprecated ControlFlowNode getValue(string name) { + result = this.getObject(name) + } + + /** Gets the flow node corresponding to the object of the attribute expression corresponding to this flow node, + with the matching name */ + ControlFlowNode getObject(string name) { + exists(Attribute a | + this.getNode() = a and a.getObject() = result.getNode() and + a.getName() = name and + result.getBasicBlock().dominates(this.getBasicBlock())) + } + + /** Gets the attribute name of the attribute expression corresponding to this flow node */ + string getName() { + exists(Attribute a | this.getNode() = a and a.getName() = result) + } + + override Attribute getNode() { result = super.getNode() } + +} + +/** A control flow node corresponding to a `from ... import ...` expression */ +class ImportMemberNode extends ControlFlowNode { + 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(ImportMember i | + this.getNode() = i and i.getModule() = result.getNode() | + i.getName() = name and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + 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 ImportExpr + } + + override ImportExpr getNode() { result = super.getNode() } + +} + +/** A control flow node corresponding to a `from ... import *` statement */ +class ImportStarNode extends ControlFlowNode { + + 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(ImportStar i | + this.getNode() = i and i.getModuleExpr() = result.getNode() | + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + 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 Subscript + } + + /** DEPRECATED: Use `getObject()` instead. + * This will be formally deprecated before the end 2018 and removed in 2019.*/ + ControlFlowNode getValue() { + exists(Subscript s | this.getNode() = s and s.getObject() = result.getNode() and + result.getBasicBlock().dominates(this.getBasicBlock())) + } + + /** flow node corresponding to the value of the sequence in a subscript operation */ + ControlFlowNode getObject() { + exists(Subscript s | this.getNode() = s and s.getObject() = result.getNode() and + result.getBasicBlock().dominates(this.getBasicBlock())) + } + + /** flow node corresponding to the index in a subscript operation */ + ControlFlowNode getIndex() { + exists(Subscript s | this.getNode() = s and s.getIndex() = result.getNode() and + result.getBasicBlock().dominates(this.getBasicBlock())) + } + + override Subscript getNode() { result = super.getNode() } +} + +/** A control flow node corresponding to a comparison operation, such as `x DeletionNode -> NameNode('b') -> AttrNode('y') -> DeletionNode`. + */ +class DeletionNode extends ControlFlowNode { + + DeletionNode() { + toAst(this) instanceof Delete + } + + /** Gets the unique target of this deletion node. */ + ControlFlowNode getTarget() { + result.getASuccessor() = this + } + +} + +/** A control flow node corresponding to a sequence (tuple or list) literal */ +abstract class SequenceNode extends ControlFlowNode { + SequenceNode() { + toAst(this) instanceof Tuple + or + toAst(this) instanceof List + } + + /** Gets the control flow node for an element of this sequence */ + ControlFlowNode getAnElement() { + result = this.getElement(_) + } + + /** Gets the control flow node for the nth element of this sequence */ + abstract ControlFlowNode getElement(int n); + +} + +/** A control flow node corresponding to a tuple expression such as `( 1, 3, 5, 7, 9 )` */ +class TupleNode extends SequenceNode { + TupleNode() { + toAst(this) instanceof Tuple + } + + override ControlFlowNode getElement(int n) { + exists(Tuple t | this.getNode() = t and result.getNode() = t.getElt(n)) and + ( + result.getBasicBlock().dominates(this.getBasicBlock()) + or + this.getBasicBlock().dominates(result.getBasicBlock()) + ) + } +} + +/** A control flow node corresponding to a list expression, such as `[ 1, 3, 5, 7, 9 ]` */ +class ListNode extends SequenceNode { + ListNode() { + toAst(this) instanceof List + } + + override ControlFlowNode getElement(int n) { + exists(List l | this.getNode() = l and result.getNode() = l.getElt(n)) and + ( + result.getBasicBlock().dominates(this.getBasicBlock()) + or + this.getBasicBlock().dominates(result.getBasicBlock()) + ) + } + +} + +class SetNode extends ControlFlowNode { + + SetNode() { + toAst(this) instanceof Set + } + + ControlFlowNode getAnElement() { + exists(Set s | this.getNode() = s and result.getNode() = s.getElt(_)) and + ( + result.getBasicBlock().dominates(this.getBasicBlock()) + or + this.getBasicBlock().dominates(result.getBasicBlock()) + ) + } + +} + +/** A control flow node corresponding to a dictionary literal, such as `{ 'a': 1, 'b': 2 }` */ +class DictNode extends ControlFlowNode { + + 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(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(Dict d | this.getNode() = d and result.getNode() = d.getAValue()) and + result.getBasicBlock().dominates(this.getBasicBlock()) + } + +} + +private Expr assigned_value(Expr lhs) { + /* lhs = result */ + exists(Assign a | a.getATarget() = lhs and result = a.getValue()) + or + /* import result as lhs */ + exists(Alias a | a.getAsname() = lhs and result = a.getValue()) + or + /* lhs += x => result = (lhs + x) */ + exists(AugAssign a, BinaryExpr b | b = a.getOperation() and result = b and lhs = b.getLeft()) + or + /* ..., lhs, ... = ..., result, ... */ + exists(Assign a, Tuple target, Tuple values, int index | + a.getATarget() = target and + a.getValue() = values and + lhs = target.getElt(index) and + result = values.getElt(index) + ) +} + +/** A flow node for a `for` statement. */ +class ForNode extends ControlFlowNode { + + ForNode() { + toAst(this) instanceof For + } + + override For getNode() { result = super.getNode() } + + /** Whether this `for` statement causes iteration over `sequence` storing each step of the iteration in `target` */ + predicate iterates(ControlFlowNode target, ControlFlowNode sequence) { + exists(For for | + toAst(this) = for and + for.getTarget() = target.getNode() and + for.getIter() = sequence.getNode() | + sequence.getBasicBlock().dominates(this.getBasicBlock()) and + sequence.getBasicBlock().dominates(target.getBasicBlock()) + ) + } + +} + +/** A flow node for a `raise` statement */ +class RaiseStmtNode extends ControlFlowNode { + + RaiseStmtNode() { + toAst(this) instanceof Raise + } + + /** Gets the control flow node for the exception raised by this raise statement */ + ControlFlowNode getException() { + exists(Raise r | + r = toAst(this) and + r.getException() = toAst(result) and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + +} + +private +predicate defined_by(NameNode def, Variable v) { + def.defines(v) or + exists(NameNode p | defined_by(p, v) and p.getASuccessor() = def and not p.defines(v)) +} + +/** A basic block (ignoring exceptional flow edges to scope exit) */ +class BasicBlock extends @py_flow_node { + + BasicBlock() { + py_flow_bb_node(_, _, this, _) + } + + /** Whether this basic block contains the specified node */ + predicate contains(ControlFlowNode node) { + py_flow_bb_node(node, _, this, _) + } + + /** Gets the nth node in this basic block */ + ControlFlowNode getNode(int n) { + py_flow_bb_node(result, _, this, n) + } + + string toString() { + result = "BasicBlock" + } + + /** Whether this basic block strictly dominates the other */ + pragma[nomagic] predicate strictlyDominates(BasicBlock other) { + other.getImmediateDominator+() = this + } + + /** Whether this basic block dominates the other */ + pragma[nomagic] predicate dominates(BasicBlock other) { + this = other + or + this.strictlyDominates(other) + } + + BasicBlock getImmediateDominator() { + this.firstNode().getImmediateDominator().getBasicBlock() = result + } + + /** Dominance frontier of a node x is the set of all nodes `other` such that `this` dominates a predecessor + * of `other` but does not strictly dominate `other` */ + predicate dominanceFrontier(BasicBlock other) { + this.dominates(other.getAPredecessor()) and not this.strictlyDominates(other) + } + + private ControlFlowNode firstNode() { + result = this + } + + /** Gets the last node in this basic block */ + ControlFlowNode getLastNode() { + exists(int i | + this.getNode(i) = result and + i = max(int j | py_flow_bb_node(_, _, this, j)) + ) + } + + private predicate oneNodeBlock() { + this.firstNode() = this.getLastNode() + } + + private predicate startLocationInfo(string file, int line, int col) { + 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 Scope and not this.oneNodeBlock()) then + this.getLastNode().getAPredecessor().getLocation().hasLocationInfo(_, _, _, endl, endc) + else + this.getLastNode().getLocation().hasLocationInfo(_, _, _, endl, endc) + } + + /** Gets a successor to this basic block */ + BasicBlock getASuccessor() { + result = this.getLastNode().getASuccessor().getBasicBlock() + } + + /** Gets a predecessor to this basic block */ + BasicBlock getAPredecessor() { + result.getASuccessor() = this + } + + /** Whether flow from this basic block reaches a normal exit from its scope */ + predicate reachesExit() { + exists(Scope s | s.getANormalExit().getBasicBlock() = this) + or + this.getASuccessor().reachesExit() + } + + predicate hasLocationInfo(string file, int line, int col, int endl, int endc) { + this.startLocationInfo(file, line, col) + and + this.endLocationInfo(endl, endc) + } + + /** Gets a true successor to this basic block */ + BasicBlock getATrueSuccessor() { + result = this.getLastNode().getATrueSuccessor().getBasicBlock() + } + + /** Gets a false successor to this basic block */ + BasicBlock getAFalseSuccessor() { + result = this.getLastNode().getAFalseSuccessor().getBasicBlock() + } + + /** Gets the scope of this block */ + pragma [nomagic] 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 + not py_scope_flow(n, _, 0) and + not py_scope_flow(n, _, 2) and + result = n.getScope() + or + py_scope_flow(n, result, _) + ) + } + + /** Whether (as inferred by type inference) it is highly unlikely (or impossible) for control to flow from this to succ. + */ + predicate unlikelySuccessor(BasicBlock succ) { + this.getLastNode().(RaisingNode).unlikelySuccessor(succ.firstNode()) + or + not end_bb_likely_reachable(this) and succ = this.getASuccessor() + } + + /** Holds if this basic block strictly reaches the other. Is the start of other reachable from the end of this. */ + predicate strictlyReaches(BasicBlock other) { + this.getASuccessor+() = other + } + + /** Holds if this basic block reaches the other. Is the start of other reachable from the end of this. */ + predicate reaches(BasicBlock other) { + this = other or this.strictlyReaches(other) + } + + /** Whether (as inferred by type inference) this basic block is likely to be reachable. + */ + predicate likelyReachable() { + start_bb_likely_reachable(this) + } +} + +private predicate start_bb_likely_reachable(BasicBlock b) { + exists(Scope s | s.getEntryNode() = b.getNode(_)) + or + exists(BasicBlock pred | + pred = b.getAPredecessor() and + end_bb_likely_reachable(pred) and + not pred.getLastNode().(RaisingNode).unlikelySuccessor(b) + ) +} + +private predicate end_bb_likely_reachable(BasicBlock b) { + start_bb_likely_reachable(b) and + not exists(ControlFlowNode p, ControlFlowNode s | + p.(RaisingNode).unlikelySuccessor(s) and + p = b.getNode(_) and + s = b.getNode(_) and + not p = b.getLastNode() + ) +} + diff --git a/python/ql/src/semmle/python/Function.qll b/python/ql/src/semmle/python/Function.qll new file mode 100644 index 000000000000..b2c1678b24d8 --- /dev/null +++ b/python/ql/src/semmle/python/Function.qll @@ -0,0 +1,377 @@ +import python + +/** A function, independent of defaults and binding. + It is the syntactic entity that is compiled to a code object. */ +class Function extends Function_, Scope, AstNode { + + /** The expression defining this function */ + CallableExpr getDefinition() { + result = this.getParent() + } + + /** The scope in which this function occurs, will be a class for a method, + * another function for nested functions, generator expressions or comprehensions, + * or a module for a plain function. */ + override Scope getEnclosingScope() { + result = this.getParent().(Expr).getScope() + } + + override Scope getScope() { + result = this.getEnclosingScope() + } + + /** Whether this function is declared in a class */ + predicate isMethod() { + exists(Class cls | this.getEnclosingScope() = cls) + } + + /** Whether this is a special method, that is does its name have the form `__xxx__` (except `__init__`) */ + predicate isSpecialMethod() { + this.isMethod() and + exists(string name | this.getName() = name | + name.matches("\\_\\_%\\_\\_") and + name != "__init__") + } + + /** Whether this function is a generator function, + that is whether it contains a yield or yield-from expression */ + predicate isGenerator() { + exists(Yield y | y.getScope() = this) + or + exists(YieldFrom y | y.getScope() = this) + } + + /** Whether this function is declared in a class and is named "__init__" */ + predicate isInitMethod() { + this.isMethod() and this.getName() = "__init__" + } + + /** Gets a decorator of this function */ + Expr getADecorator() { + result = ((FunctionExpr)this.getDefinition()).getADecorator() + } + + /** Gets the name of the nth argument (for simple arguments) */ + string getArgName(int index) { + result = ((Name)this.getArg(index)).getId() + } + + Parameter getArgByName(string name) { + result = this.getAnArg() and + result.(Name).getId() = name + } + + override Location getLocation() { + py_scope_location(result, this) + } + + override string toString() { + result = "Function " + this.getName() + } + + /** Gets the statements forming the body of this function */ + override StmtList getBody() { + result = Function_.super.getBody() + } + + /** Gets the nth statement in the function */ + override Stmt getStmt(int index) { + result = Function_.super.getStmt(index) + } + + /** Gets a statement in the function */ + override Stmt getAStmt() { + result = Function_.super.getAStmt() + } + + /** Gets the name used to define this function */ + override string getName() { + result = Function_.super.getName() + } + + /** Gets the metrics for this function */ + FunctionMetrics getMetrics() { + result = this + } + + /** Gets the FunctionObject corresponding to this function */ + FunctionObject getFunctionObject() { + result.getOrigin() = this.getDefinition() + } + + /** Whether this function is a procedure, that is, it has no explicit return statement and always returns None. + * Note that generator and async functions are not procedures as they return generators and coroutines respectively. */ + predicate isProcedure() { + not exists(this.getReturnNode()) and exists(this.getFallthroughNode()) and not this.isGenerator() and not this.isAsync() + } + + /** Gets the number of positional parameters */ + int getPositionalParameterCount() { + result = count(this.getAnArg()) + } + + /** Gets the number of keyword-only parameters */ + int getKeywordOnlyParameterCount() { + result = count(this.getAKwonlyarg()) + } + + /** Whether this function accepts a variable number of arguments. That is, whether it has a starred (*arg) parameter. */ + predicate hasVarArg() { + exists(this.getVararg()) + } + + /** Whether this function accepts arbitrary keyword arguments. That is, whether it has a double-starred (**kwarg) parameter. */ + predicate hasKwArg() { + exists(this.getKwarg()) + } + + override AstNode getAChildNode() { + result = this.getAStmt() or + result = this.getAnArg() or + result = this.getVararg() or + result = this.getKwarg() + } + + /** Gets the qualified name for this function. + * Should return the same name as the `__qualname__` attribute on functions in Python 3. + */ + string getQualifiedName() { + this.getEnclosingScope() instanceof Module and result = this.getName() + or + exists(string enclosing_name | + enclosing_name = this.getEnclosingScope().(Function).getQualifiedName() + or + enclosing_name = this.getEnclosingScope().(Class).getQualifiedName() | + result = enclosing_name + "." + this.getName() + ) + } + + /** Gets the nth keyword-only parameter of this function. */ + Name getKeywordOnlyArg(int n) { + result = Function_.super.getKwonlyarg(n) + } + + /** Gets a keyword-only parameter of this function. */ + Name getAKeywordOnlyArg() { + result = this.getKeywordOnlyArg(_) + } + + override Scope getEvaluatingScope() { + major_version() = 2 and exists(Comp comp | comp.getFunction() = this | result = comp.getEvaluatingScope()) + or + not exists(Comp comp | comp.getFunction() = this) and result = this + or + major_version() = 3 and result = this + } + + override + predicate containsInScope(AstNode inner) { + Scope.super.containsInScope(inner) + } + + override + predicate contains(AstNode inner) { + Scope.super.contains(inner) + } + +} + +/** A def statement. Note that FunctionDef extends Assign as a function definition binds the newly created function */ +class FunctionDef extends Assign { + + FunctionDef() { + /* This is an artificial assignment the rhs of which is a (possibly decorated) FunctionExpr */ + exists(FunctionExpr f | this.getValue() = f or this.getValue() = f.getADecoratorCall()) + } + + override string toString() { + result = "FunctionDef" + } + + /** Gets the function for this statement */ + Function getDefinedFunction() { + exists(FunctionExpr func | this.containsInScope(func) and result = func.getInnerScope()) + } + + override Stmt getLastStatement() { + result = this.getDefinedFunction().getLastStatement() + } + +} + +class FastLocalsFunction extends Function { + + /** A function that uses 'fast' locals, stored in the frame not in a dictionary. */ + FastLocalsFunction () { + not exists(ImportStar i | i.getScope() = this) + and + not exists(Exec e | e.getScope() = this) + } + +} + +/** A parameter. Either a Tuple or a Name (always a Name for Python 3) */ +class Parameter extends Parameter_ { + + Parameter() { + /* Parameter_ is just defined as a Name or Tuple, narrow to actual parameters */ + exists(ParameterList pl | py_exprs(this, _, pl, _)) + } + + Location getLocation() { + result = this.asName().getLocation() + or + result = this.asTuple().getLocation() + } + + /** Gets this parameter if it is a Name (not a Tuple) */ + Name asName() { + result = this + } + + /** Gets this parameter if it is a Tuple (not a Name) */ + Tuple asTuple() { + result = this + } + + Expr getDefault() { + exists(Function f, int n, int c, int d, Arguments args | + args = f.getDefinition().getArgs() | + f.getArg(n) = this and + c = count(f.getAnArg()) and + d = count(args.getADefault()) and + result = args.getDefault(d-c+n) + ) + } + + Variable getVariable() { + result.getAnAccess() = this.asName() + } + + /** Gets the position of this parameter */ + int getPosition() { + exists(Function f | + f.getArg(result) = this + ) + } + + /** Gets the name of this parameter */ + string getName() { + result = this.asName().getId() + } + + /** Holds if this parameter is the first parameter of a method. It is not necessarily called "self" */ + predicate isSelf() { + exists(Function f | + f.getArg(0) = this and + f.isMethod() + ) + } + + /** Holds if this parameter is a 'varargs' parameter. + * The `varargs` in `f(a, b, *varargs)`. + */ + predicate isVarargs() { + exists(Function func | func.getVararg() = this) + } + + /** Holds if this parameter is a 'kwargs' parameter. + * The `kwargs` in `f(a, b, **kwargs)`. + */ + predicate isKwargs() { + exists(Function func | func.getKwarg() = this) + } + +} + +/** An expression that generates a callable object, either a function expression or a lambda */ +abstract class CallableExpr extends Expr { + + /** Gets the parameters of this callable. + * This predicate is called getArgs(), rather than getParameters() for compatibility with Python's AST module. */ + abstract Arguments getArgs(); + + /** Gets the function scope of this code expression. */ + abstract Function getInnerScope(); + +} + +/** An (artificial) expression corresponding to a function definition. */ +class FunctionExpr extends FunctionExpr_, CallableExpr { + + override Expr getASubExpression() { + result = this.getArgs().getASubExpression() or + result = this.getReturns() + } + + override predicate hasSideEffects() { + any() + } + + Call getADecoratorCall() { + result.getArg(0) = this or + result.getArg(0) = this.getADecoratorCall() + } + + /** Gets a decorator of this function expression */ + Expr getADecorator() { + result = this.getADecoratorCall().getFunc() + } + + override AstNode getAChildNode() { + result = this.getASubExpression() + or + result = this.getInnerScope() + } + + override Function getInnerScope() { + result = FunctionExpr_.super.getInnerScope() + } + + override Arguments getArgs() { + result = FunctionExpr_.super.getArgs() + } + +} + +/** A lambda expression, such as lambda x:x*x */ +class Lambda extends Lambda_, CallableExpr { + + /** Gets the expression to the right of the colon in this lambda expression */ + Expr getExpression() { + exists(Return ret | ret = this.getInnerScope().getStmt(0) | + result = ret.getValue()) + } + + override Expr getASubExpression() { + result = this.getArgs().getASubExpression() + } + + override AstNode getAChildNode() { + result = this.getASubExpression() or + result = this.getInnerScope() + } + + override Function getInnerScope() { + result = Lambda_.super.getInnerScope() + } + + override Arguments getArgs() { + result = Lambda_.super.getArgs() + } + +} + +/** The arguments in a function definition */ +class Arguments extends Arguments_ { + + Expr getASubExpression() { + result = this.getAKwDefault() or + result = this.getAnAnnotation() or + result = this.getKwargannotation() or + result = this.getVarargannotation() or + result = this.getADefault() + } +} + + diff --git a/python/ql/src/semmle/python/GuardedControlFlow.qll b/python/ql/src/semmle/python/GuardedControlFlow.qll new file mode 100644 index 000000000000..d51eb1b741e6 --- /dev/null +++ b/python/ql/src/semmle/python/GuardedControlFlow.qll @@ -0,0 +1,67 @@ +import python + +/** A basic block which terminates in a condition, splitting the subsequent control flow */ +class ConditionBlock extends BasicBlock { + + ConditionBlock() { + exists(ControlFlowNode succ | succ = this.getATrueSuccessor() or succ = this.getAFalseSuccessor()) + } + + /** Basic blocks controlled by this condition, i.e. those BBs for which the condition is testIsTrue */ + predicate controls(BasicBlock controlled, boolean testIsTrue) { + /* For this block to control the block 'controlled' with 'testIsTrue' the following must be true: + Execution must have passed through the test i.e. 'this' must strictly dominate 'controlled'. + Execution must have passed through the 'testIsTrue' edge leaving 'this'. + + Although "passed through the true edge" implies that this.getATrueSuccessor() dominates 'controlled', + the reverse is not true, as flow may have passed through another edge to get to this.getATrueSuccessor() + so we need to assert that this.getATrueSuccessor() dominates 'controlled' *and* that + all predecessors of this.getATrueSuccessor() are either this or dominated by this.getATrueSuccessor(). + + For example, in the following python snippet: + + if x: + controlled + false_successor + uncontrolled + + false_successor dominates uncontrolled, but not all of its predecessors are this (if x) + or dominated by itself. Whereas in the following code: + + if x: + while controlled: + also_controlled + false_successor + uncontrolled + + the block 'while controlled' is controlled because all of its predecessors are this (if x) + or (in the case of 'also_controlled') dominated by itself. + + The additional constraint on the predecessors of the test successor implies + that `this` strictly dominates `controlled` so that isn't necessary to check + directly. + */ + exists(BasicBlock succ | + testIsTrue = true and succ = this.getATrueSuccessor() + or + testIsTrue = false and succ = this.getAFalseSuccessor() + | + succ.dominates(controlled) and + forall(BasicBlock pred | pred.getASuccessor() = succ | + pred = this or succ.dominates(pred) + ) + ) + } + + /** Holds if this condition controls the edge `pred->succ`, i.e. those edges for which the condition is `testIsTrue`. */ + predicate controlsEdge(BasicBlock pred, BasicBlock succ, boolean testIsTrue) { + this.controls(pred, testIsTrue) and succ = pred.getASuccessor() + or + pred = this and ( + testIsTrue = true and succ = this.getATrueSuccessor() + or + testIsTrue = false and succ = this.getAFalseSuccessor() + ) + } + +} diff --git a/python/ql/src/semmle/python/Import.qll b/python/ql/src/semmle/python/Import.qll new file mode 100644 index 000000000000..0668c97bdcf7 --- /dev/null +++ b/python/ql/src/semmle/python/Import.qll @@ -0,0 +1,275 @@ +import python + + +/** An alias in an import statement, the `mod as name` part of `import mod as name`. May be artificial; + `import x` is transformed into `import x as x` */ +class Alias extends Alias_ { + + Location getLocation() { + result = this.getValue().getLocation() + } + +} + +private predicate valid_module_name(string name) { + exists(Module m | m.getName() = name) + or + exists(Object cmod | py_cobjecttypes(cmod, theModuleType()) and py_cobjectnames(cmod, name)) +} + +/** An artificial expression representing an import */ +class ImportExpr extends ImportExpr_ { + + + private string basePackageName(int n) { + n = 1 and result = this.getEnclosingModule().getPackageName() + or + exists(string bpnm1 | bpnm1 = this.basePackageName(n-1) and + bpnm1.matches("%.%") and + result = bpnm1.regexpReplaceAll("\\.[^.]*$", "") + ) + } + + private predicate implicitRelativeImportsAllowed() { + // relative imports are no longer allowed in Python 3 + major_version() < 3 and + // and can be explicitly turned off in later versions of Python 2 + not getEnclosingModule().hasFromFuture("absolute_import") + } + + /** The language specifies level as -1 if relative imports are to be tried first, 0 for absolute imports, + and level > 0 for explicit relative imports. */ + override int getLevel() { + exists(int l | l = super.getLevel() | + l > 0 and result = l + or + /* The extractor may set level to 0 even though relative imports apply */ + l = 0 and ( + if this.implicitRelativeImportsAllowed() then + result = -1 + else + result = 0 + ) + ) + } + + /** + * If this import is relative, and relative imports are allowed, compute + * the name of the topmost module that will be imported. + */ + private string relativeTopName() { + getLevel() = -1 and + result = basePackageName(1) + "." + this.getTopName() and + valid_module_name(result) + } + + private string qualifiedTopName() { + if (this.getLevel() <= 0) then ( + result = this.getTopName() + ) else ( + result = basePackageName(this.getLevel()) and + valid_module_name(result) + ) + } + + /** Gets the name by which the lowest level module or package is imported. + * NOTE: This is the name that used to import the module, + * which may not be the name of the module. */ + string bottomModuleName() { + result = relativeTopName() + this.remainderOfName() + or + ( + not exists(relativeTopName()) and + result = this.qualifiedTopName() + this.remainderOfName() + ) + } + + /** Gets the name of topmost module or package being imported */ + string topModuleName() { + result = relativeTopName() + or + ( + not exists(relativeTopName()) and + result = this.qualifiedTopName() + ) + } + + /** Gets the full name of the module resulting from evaluating this import. + * NOTE: This is the name that used to import the module, + * which may not be the name of the module. */ + string getImportedModuleName() { + exists(string bottomName | bottomName = this.bottomModuleName() | + if this.isTop() then + result = topModuleName() + else + result = bottomName + ) + } + + /** Gets the names of the modules that may be imported by this import. + * For example this predicate would return 'x' and 'x.y' for `import x.y` + */ + string getAnImportedModuleName() { + result = this.bottomModuleName() + or + result = this.getAnImportedModuleName().regexpReplaceAll("\\.[^.]*$", "") + } + + override Expr getASubExpression() { + none() + } + + override predicate hasSideEffects() { + any() + } + + private string getTopName() { + result = this.getName().regexpReplaceAll("\\..*", "") + } + + private string remainderOfName() { + not exists(this.getName()) and result = "" or + this.getLevel() <= 0 and result = this.getName().regexpReplaceAll("^[^\\.]*", "") or + this.getLevel() > 0 and result = "." + this.getName() + } + + /** Whether this import is relative, that is not absolute. + * See https://www.python.org/dev/peps/pep-0328/ */ + predicate isRelative() { + /* Implicit */ + exists(this.relativeTopName()) + or + /* Explicit */ + this.getLevel() > 0 + } + +} + +/** A `from ... import ...` expression */ +class ImportMember extends ImportMember_ { + + override Expr getASubExpression() { + result = this.getModule() + } + + override predicate hasSideEffects() { + /* Strictly this only has side-effects if the module is a package */ + any() + } + + /** Gets the full name of the module resulting from evaluating this import. + * NOTE: This is the name that used to import the module, + * which may not be the name of the module. */ + string getImportedModuleName() { + result = this.getModule().(ImportExpr).getImportedModuleName() + "." + this.getName() + } + + override ImportMemberNode getAFlowNode() { result = super.getAFlowNode() } +} + +/** An import statement */ +class Import extends Import_ { + + private ImportExpr getAModuleExpr() { + result = this.getAName().getValue() + or + result = ((ImportMember)this.getAName().getValue()).getModule() + } + + /** Use getAnImportedModuleName(), + * possibly combined with ModuleObject.importedAs() + * Gets a module imported by this import statement */ + deprecated Module getAModule() { + result.getName() = this.getAnImportedModuleName() + } + + /** Whether this a `from ... import ...` statement */ + predicate isFromImport() { + this.getAName().getValue() instanceof ImportMember + } + + override Expr getASubExpression() { + result = this.getAModuleExpr() or + result = this.getAName().getAsname() or + result = this.getAName().getValue() + } + + override Stmt getASubStatement() { + none() + } + + /** Gets the name of an imported module. + * For example, for the import statement `import bar` which + * is a relative import in package "foo", this would return + * "foo.bar". + * The import statment `from foo import bar` would return + * `foo` and `foo.bar` + * */ + string getAnImportedModuleName() { + result = this.getAModuleExpr().getAnImportedModuleName() + or + exists(ImportMember m, string modname | + m = this.getAName().getValue() and + modname = m.getModule().(ImportExpr).getImportedModuleName() | + result = modname + or + result = modname + "." + m.getName() + ) + } + +} + +/** An import * statement */ +class ImportStar extends ImportStar_ { + + ImportExpr getModuleExpr() { + result = this.getModule() + or + result = ((ImportMember)this.getModule()).getModule() + } + + override string toString() { + result = "from " + this.getModuleExpr().getName() + " import *" + } + + /** Use getAnImportedModuleName(), + * possibly combined with ModuleObject.importedAs() + * Gets the module imported by this import * statement + */ + deprecated Module getTheModule() { + result.getName() = this.getImportedModuleName() + } + + override Expr getASubExpression() { + result = this.getModule() + } + + override Stmt getASubStatement() { + none() + } + + /** Gets the name of the imported module. */ + string getImportedModuleName() { + result = this.getModuleExpr().getImportedModuleName() + } + +} + +/** A statement that imports a module. This can be any statement that includes the `import` keyword, + * such as `import sys`, `from sys import version` or `from sys import *`. */ +class ImportingStmt extends Stmt { + + ImportingStmt() { + this instanceof Import + or + this instanceof ImportStar + } + + /** Gets the name of an imported module. */ + string getAnImportedModuleName() { + result = this.(Import).getAnImportedModuleName() + or + result = this.(ImportStar).getImportedModuleName() + } + +} diff --git a/python/ql/src/semmle/python/Keywords.qll b/python/ql/src/semmle/python/Keywords.qll new file mode 100644 index 000000000000..3be9311d081f --- /dev/null +++ b/python/ql/src/semmle/python/Keywords.qll @@ -0,0 +1,101 @@ +import python + +class KeyValuePair extends KeyValuePair_, DictDisplayItem { + + override Location getLocation() { + result = KeyValuePair_.super.getLocation() + } + + override string toString() { + result = KeyValuePair_.super.toString() + } + + /** Gets the value of this dictionary unpacking. */ + override Expr getValue() { + result = KeyValuePair_.super.getValue() + } + + override Scope getScope() { + result = this.getValue().getScope() + } + + override AstNode getAChildNode() { + result = this.getKey() + or + result = this.getValue() + } + +} + +/** A double-starred expression in a call or dict literal. */ +class DictUnpacking extends DictUnpacking_, DictUnpackingOrKeyword, DictDisplayItem { + + override Location getLocation() { + result = DictUnpacking_.super.getLocation() + } + + override string toString() { + result = DictUnpacking_.super.toString() + } + + /** Gets the value of this dictionary unpacking. */ + override Expr getValue() { + result = DictUnpacking_.super.getValue() + } + + override Scope getScope() { + result = this.getValue().getScope() + } + + override AstNode getAChildNode() { + result = this.getValue() + } + +} + +abstract class DictUnpackingOrKeyword extends DictItem { + + abstract Expr getValue(); + + override string toString() { + none() + } + +} + +abstract class DictDisplayItem extends DictItem { + + abstract Expr getValue(); + + override string toString() { + none() + } + +} + +/** A keyword argument in a call. For example `arg=expr` in `foo(0, arg=expr)` */ +class Keyword extends Keyword_, DictUnpackingOrKeyword { + + override Location getLocation() { + result = Keyword_.super.getLocation() + } + + override string toString() { + result = Keyword_.super.toString() + } + + /** Gets the value of this keyword argument. */ + override Expr getValue() { + result = Keyword_.super.getValue() + } + + override Scope getScope() { + result = this.getValue().getScope() + } + + override AstNode getAChildNode() { + result = this.getValue() + } + +} + diff --git a/python/ql/src/semmle/python/Lists.qll b/python/ql/src/semmle/python/Lists.qll new file mode 100644 index 000000000000..cb6bbb1e3f5c --- /dev/null +++ b/python/ql/src/semmle/python/Lists.qll @@ -0,0 +1,55 @@ +import python + +/** A parameter list */ +class ParameterList extends @py_parameter_list { + + Function getParent() { + py_parameter_lists(this, result) + } + + /** Gets a parameter */ + Parameter getAnItem() { + /* Item can be a Name or a Tuple, both of which are expressions */ + py_exprs(result, _, this, _) + } + + /** Gets the nth parameter */ + Parameter getItem(int index) { + /* Item can be a Name or a Tuple, both of which are expressions */ + py_exprs(result, _, this, index) + } + + string toString() { + result = "ParameterList" + } +} + +/** A list of Comprehensions (for generating parts of a set, list or dictionary comprehension) */ +class ComprehensionList extends ComprehensionList_ { + +} + +/** A list of expressions */ +class ExprList extends ExprList_ { + +} + + +library class DictItemList extends DictItemList_ { + +} + +library class DictItemListParent extends DictItemListParent_ { + +} + +/** A list of strings (the primitive type string not Bytes or Unicode) */ +class StringList extends StringList_ { + +} + +/** A list of aliases in an import statement */ +class AliasList extends AliasList_ { + +} + diff --git a/python/ql/src/semmle/python/Metrics.qll b/python/ql/src/semmle/python/Metrics.qll new file mode 100644 index 000000000000..dde48db0fc85 --- /dev/null +++ b/python/ql/src/semmle/python/Metrics.qll @@ -0,0 +1,388 @@ +import python + +/** The metrics for a function */ +class FunctionMetrics extends Function { + + /** Gets the total number of lines (including blank lines) + from the definition to the end of the function */ + int getNumberOfLines() { + py_alllines(this, result) + } + + /** Gets the number of lines of code in the function */ + int getNumberOfLinesOfCode() { + py_codelines(this, result) + } + + /** Gets the number of lines of comments in the function */ + int getNumberOfLinesOfComments() { + py_commentlines(this, result) + } + + /** Gets the number of lines of docstring in the function */ + int getNumberOfLinesOfDocStrings() { + py_docstringlines(this, result) + } + + /** Cyclomatic complexity: + * The number of linearly independent paths through the source code. + * Computed as E - N + 2P, + * where + * E = the number of edges of the graph. + * N = the number of nodes of the graph. + * P = the number of connected components, which for a single function is 1. + */ + int getCyclomaticComplexity() { + exists(int E, int N | + N = count(BasicBlock b | b = this.getABasicBlock() and b.likelyReachable()) + and + E = count(BasicBlock b1, BasicBlock b2 | + b1 = this.getABasicBlock() and b1.likelyReachable() and + b2 = this.getABasicBlock() and b2.likelyReachable() and + b2 = b1.getASuccessor() and not b1.unlikelySuccessor(b2) + ) + | + result = E - N + 2 + ) + } + + private BasicBlock getABasicBlock() { + result = this.getEntryNode().getBasicBlock() + or + exists(BasicBlock mid | mid = this.getABasicBlock() and result = mid.getASuccessor()) + } + + /** Dependency of Callables + One callable "this" depends on another callable "result" + if "this" makes some call to a method that may end up being "result". + */ + FunctionMetrics getADependency() { + result != this and + not non_coupling_method(result) and + exists(Call call | + call.getScope() = this | + exists(FunctionObject callee | + callee.getFunction() = result | + call.getAFlowNode().getFunction().refersTo(callee) + ) + or + exists(Attribute a | + call.getFunc() = a | + unique_root_method(result, a.getName()) or + exists(Name n | a.getObject() = n and n.getId() = "self" | + result.getScope() = this.getScope() and + result.getName() = a.getName() + ) + ) + ) + } + + /** Afferent Coupling + the number of callables that depend on this method. + This is sometimes called the "fan-in" of a method. + */ + int getAfferentCoupling() { + result = count(FunctionMetrics m | m.getADependency() = this ) + } + + /** Efferent Coupling + the number of methods that this method depends on + This is sometimes called the "fan-out" of a method. + */ + int getEfferentCoupling() { + result = count(FunctionMetrics m | this.getADependency() = m) + } + + int getNumberOfParametersWithoutDefault() { + result = this.getPositionalParameterCount() - + count(((FunctionExpr)this.getDefinition()).getArgs().getADefault()) + } + + int getStatementNestingDepth() { + result = max(Stmt s | s.getScope() = this | getNestingDepth(s)) + } + + int getNumberOfCalls() { + result = count(Call c | c.getScope() = this) + } + +} + +/** The metrics for a class */ +class ClassMetrics extends Class { + + /** Gets the total number of lines (including blank lines) + from the definition to the end of the class */ + int getNumberOfLines() { + py_alllines(this, result) + } + + /** Gets the number of lines of code in the class */ + int getNumberOfLinesOfCode() { + py_codelines(this, result) + } + + /** Gets the number of lines of comments in the class */ + int getNumberOfLinesOfComments() { + py_commentlines(this, result) + } + + /** Gets the number of lines of docstrings in the class */ + int getNumberOfLinesOfDocStrings() { + py_docstringlines(this, result) + } + + private predicate dependsOn(Class other) { + other != this and + ( + exists(FunctionMetrics f1, FunctionMetrics f2 | + f1.getADependency() = f2 | + f1.getScope() = this and f2.getScope() = other + ) + or + exists(Function f, Call c, ClassObject cls | + c.getScope() = f and f.getScope() = this | + c.getFunc().refersTo(cls) and + cls.getPyClass() = other + ) + ) + } + + /** The afferent coupling of a class is the number of classes that + * directly depend on it. + */ + int getAfferentCoupling() { + result = count(ClassMetrics t | t.dependsOn(this)) + } + + /** The efferent coupling of a class is the number of classes that + * it directly depends on. + */ + int getEfferentCoupling() { + result = count(ClassMetrics t | this.dependsOn(t)) + } + + int getInheritanceDepth() { + exists(ClassObject cls | + cls.getPyClass() = this | + result = max(classInheritanceDepth(cls)) + ) + } + + /* -------- CHIDAMBER AND KEMERER LACK OF COHESION IN METHODS ------------ */ + + /* The aim of this metric is to try and determine whether a class + represents one abstraction (good) or multiple abstractions (bad). + If a class represents multiple abstractions, it should be split + up into multiple classes. + + In the Chidamber and Kemerer method, this is measured as follows: + n1 = number of pairs of distinct methods in a class that do *not* + have at least one commonly accessed field + n2 = number of pairs of distinct methods in a class that do + have at least one commonly accessed field + lcom = ((n1 - n2)/2 max 0) + + We divide by 2 because each pair (m1,m2) is counted twice in n1 and n2. + + */ + + /** should function f be excluded from the cohesion computation? */ + predicate ignoreLackOfCohesion(Function f) { + f.isInitMethod() or f.isSpecialMethod() + } + + private predicate methodPair(Function m1, Function m2) { + m1.getScope() = this and + m2.getScope() = this and + not this.ignoreLackOfCohesion(m1) and + not this.ignoreLackOfCohesion(m2) and + m1 != m2 + } + + private predicate one_accesses_other(Function m1, Function m2) { + this.methodPair(m1, m2) and + ( + exists(SelfAttributeRead sa | + sa.getName() = m1.getName() and + sa.getScope() = m2 + ) + or + exists(SelfAttributeRead sa | + sa.getName() = m2.getName() and + sa.getScope() = m1 + ) + ) + } + + + /** do m1 and m2 access a common field or one calls the other? */ + private predicate shareField(Function m1, Function m2) { + this.methodPair(m1, m2) and + exists(string name | + exists(SelfAttributeRead sa | + sa.getName() = name and + sa.getScope() = m1 + ) + and + exists(SelfAttributeRead sa | + sa.getName() = name and + sa.getScope() = m2 + ) + ) + } + + private int similarMethodPairs() { + result = count(Function m1, Function m2 | + this.methodPair(m1, m2) and + (this.shareField(m1, m2) or this.one_accesses_other(m1, m2)) + ) / 2 + } + + private int methodPairs() { + result = count(Function m1, Function m2 | this.methodPair(m1, m2)) / 2 + } + + /** return Chidamber and Kemerer Lack of Cohesion */ + int getLackOfCohesionCK() { + exists(int n | + n = this.methodPairs() - 2 * this.similarMethodPairs() + and + result = n.maximum(0) + ) + } + + private predicate similarMethodPairDag(Function m1, Function m2, int line) { + (this.shareField(m1, m2) or this.one_accesses_other(m1, m2)) and + line = m1.getLocation().getStartLine() and + line < m2.getLocation().getStartLine() + } + + private predicate subgraph(Function m, int line) { + this.similarMethodPairDag(m, _, line) and not this.similarMethodPairDag(_, m, _) + or + exists(Function other | this.subgraph(other, line) | + this.similarMethodPairDag(other, m, _) or + this.similarMethodPairDag(m, other, _) + ) + } + + predicate unionSubgraph(Function m, int line) { + line = min(int l | this.subgraph(m, l)) + } + + /** return Hitz and Montazeri Lack of Cohesion */ + int getLackOfCohesionHM() { + result = count(int line | + this.unionSubgraph(_, line) + ) + } + +} + +private int classInheritanceDepth(ClassObject cls) { + /* Prevent run-away recursion in case of circular inheritance */ + not cls.getASuperType() = cls + and + ( + exists(ClassObject sup | + cls.getABaseType() = sup | + result = classInheritanceDepth(sup) + 1 + ) + or + not exists(cls.getABaseType()) and ( + major_version() = 2 and result = 0 + or + major_version() > 2 and result = 1 + ) + ) +} + +class ModuleMetrics extends Module { + + /** Gets the total number of lines (including blank lines) in the module */ + int getNumberOfLines() { + py_alllines(this, result) + } + + /** Gets the number of lines of code in the module */ + int getNumberOfLinesOfCode() { + py_codelines(this, result) + } + + /** Gets the number of lines of comments in the module */ + int getNumberOfLinesOfComments() { + py_commentlines(this, result) + } + + /** Gets the number of lines of docstrings in the module */ + int getNumberOfLinesOfDocStrings() { + py_docstringlines(this, result) + } + + /** The afferent coupling of a class is the number of classes that + * directly depend on it. + */ + int getAfferentCoupling() { + result = count(ModuleMetrics t | t.dependsOn(this)) + } + + /** The efferent coupling of a class is the number of classes that + * it directly depends on. + */ + int getEfferentCoupling() { + result = count(ModuleMetrics t | this.dependsOn(t)) + } + + private predicate dependsOn(Module other) { + other != this and + ( + exists(FunctionMetrics f1, FunctionMetrics f2 | + f1.getADependency() = f2 | + f1.getEnclosingModule() = this and f2.getEnclosingModule() = other + ) + or + exists(Function f, Call c, ClassObject cls | + c.getScope() = f and f.getScope() = this | + c.getFunc().refersTo(cls) and + cls.getPyClass().getEnclosingModule() = other + ) + ) + } + +} + +/** Helpers for coupling */ + +predicate unique_root_method(Function func, string name) { + name = func.getName() and + not exists(FunctionObject f, FunctionObject other | + f.getFunction() = func and + other.getName() = name | + not other.overrides(f) + ) +} + +predicate non_coupling_method(Function f) { + f.isSpecialMethod() or + f.isInitMethod() or + f.getName() = "close" or + f.getName() = "write" or + f.getName() = "read" or + f.getName() = "get" or + f.getName() = "set" +} + +private int getNestingDepth(Stmt s) { + not exists(Stmt outer | outer.getASubStatement() = s) and result = 1 + or + exists(Stmt outer | + outer.getASubStatement() = s | + if s.(If).isElif() or s instanceof ExceptStmt then + /* If statement is an `elif` or `except` then it is not indented relative to its parent */ + result = getNestingDepth(outer) + else + result = getNestingDepth(outer) + 1 + ) +} + diff --git a/python/ql/src/semmle/python/Module.qll b/python/ql/src/semmle/python/Module.qll new file mode 100644 index 000000000000..d42fe53bbc5c --- /dev/null +++ b/python/ql/src/semmle/python/Module.qll @@ -0,0 +1,231 @@ +import python +private import semmle.python.pointsto.PointsTo + +/** A module. This is the top level element in an AST, corresponding to a source file. + * It is also a Scope; the scope of global variables. */ +class Module extends Module_, Scope, AstNode { + + override string toString() { + result = this.getKind() + " " + this.getName() + or + /* No name is defined, which means that this is not on an import path. So it must be a script */ + not exists(this.getName()) and not this.isPackage() and + result = "Script " + this.getFile().getShortName() + } + + /** This method will be deprecated in the next release. Please use `getEnclosingScope()` instead. + * The enclosing scope of this module (always none) */ + override Scope getScope() { + none() + } + + /** The enclosing scope of this module (always none) */ + override Scope getEnclosingScope() { + none() + } + + /** Gets the statements forming the body of this module */ + override StmtList getBody() { + result = Module_.super.getBody() + } + + /** Gets the nth statement of this module */ + override Stmt getStmt(int n) { + result = Module_.super.getStmt(n) + } + + /** Gets a top-level statement in this module */ + override Stmt getAStmt() { + result = Module_.super.getAStmt() + } + + /** Gets the name of this module */ + override string getName() { + result = Module_.super.getName() and legalDottedName(result) + or + not exists(Module_.super.getName()) and + result = moduleNameFromFile(this.getPath()) + } + + /** Gets this module */ + override Module getEnclosingModule() { + result = this + } + + /** Gets the __init__ module of this module if the module is a package and it has an __init__ module */ + Module getInitModule() { + /* this.isPackage() and */ result.getName() = this.getName() + ".__init__" + } + + /** Whether this module is a package initializer */ + predicate isPackageInit() { + this.getName().matches("%\\_\\_init\\_\\_") and not this.isPackage() + } + + /** Gets a name exported by this module, that is the names that will be added to a namespace by 'from this-module import *' */ + string getAnExport() { + py_exports(this, result) + or + not PointsTo::module_defines_name(this, "__all__") and PointsTo::module_defines_name(this, result) + } + + /** Gets the source file for this module */ + File getFile() { + py_module_path(this, result) + } + + /** Gets the source file or folder for this module or package */ + Container getPath() { + py_module_path(this, result) + } + + /** Whether this is a package */ + predicate isPackage() { + this.getPath() instanceof Folder + } + + /** Gets the package containing this module (or parent package if this is a package) */ + Module getPackage() { + this.getName().matches("%.%") and + result.getName() = getName().regexpReplaceAll("\\.[^.]*$", "") + } + + /** Gets the name of the package containing this module */ + string getPackageName() { + this.getName().matches("%.%") and + result = getName().regexpReplaceAll("\\.[^.]*$", "") + } + + /** Gets the metrics for this module */ + ModuleMetrics getMetrics() { + result = this + } + + /** Use ModuleObject.getAnImportedModule() instead. + * Gets a module imported by this module */ + deprecated Module getAnImportedModule() { + result.getName() = this.getAnImportedModuleName() + } + + string getAnImportedModuleName() { + exists(Import i | i.getEnclosingModule() = this | result = i.getAnImportedModuleName()) + or + exists(ImportStar i | i.getEnclosingModule() = this | result = i.getImportedModuleName()) + } + + override Location getLocation() { + py_scope_location(result, this) + } + + /** Gets a child module or package of this package */ + Module getSubModule(string name) { + result.getPackage() = this and + name = result.getName().regexpReplaceAll(".*\\.", "") + } + + /** Whether name is declared in the __all__ list of this module */ + predicate declaredInAll(string name) + { + exists(AssignStmt a, GlobalVariable all | + a.defines(all) and a.getScope() = this and + all.getId() = "__all__" and ((List)a.getValue()).getAnElt().(StrConst).getText() = name + ) + } + + override AstNode getAChildNode() { + result = this.getAStmt() + } + + predicate hasFromFuture(string attr) { + exists(Import i, ImportMember im, ImportExpr ie, Alias a, Name name | + im.getModule() = ie and ie.getName() = "__future__" and + a.getAsname() = name and name.getId() = attr and + i.getASubExpression() = im and + i.getAName() = a and + i.getEnclosingModule() = this + ) + } + + /** Gets the path element from which this module was loaded. */ + Container getLoadPath() { + result = this.getPath().getImportRoot() + } + + /** Holds if this module is in the standard library for version `major.minor` */ + predicate inStdLib(int major, int minor) { + this.getLoadPath().isStdLibRoot(major, minor) + } + + /** Holds if this module is in the standard library */ + predicate inStdLib() { + this.inStdLib(_, _) + } + + override + predicate containsInScope(AstNode inner) { + Scope.super.containsInScope(inner) + } + + override + predicate contains(AstNode inner) { + Scope.super.contains(inner) + } + + /** Gets the kind of this module. */ + override string getKind() { + if this.isPackage() then + result = "Package" + else ( + not exists(Module_.super.getKind()) and result = "Module" + or + result = Module_.super.getKind() + ) + } + +} + +bindingset[name] +private predicate legalDottedName(string name) { + name.regexpMatch("(\\p{L}|_)(\\p{L}|\\d|_)*(\\.(\\p{L}|_)(\\p{L}|\\d|_)*)*") +} + +bindingset[name] +private predicate legalShortName(string name) { + name.regexpMatch("(\\p{L}|_)(\\p{L}|\\d|_)*") +} + +/** Holds if `f` is potentially a source package. + * Does it have an __init__.py file and is it within the source archive? + */ +private predicate isPotentialSourcePackage(Folder f) { + f.getRelativePath() != "" and + exists(f.getFile("__init__.py")) +} + +private string moduleNameFromBase(Container file) { + file instanceof Folder and result = file.getBaseName() + or + file instanceof File and result = file.getStem() +} + +private string moduleNameFromFile(Container file) { + exists(string basename | + basename = moduleNameFromBase(file) and + legalShortName(basename) + | + result = moduleNameFromFile(file.getParent()) + "." + basename + or + isPotentialSourcePackage(file) and result = file.getStem() and + (not isPotentialSourcePackage(file.getParent()) or not legalShortName(file.getParent().getBaseName())) + or + result = file.getStem() and file.getParent() = file.getImportRoot() + or + result = file.getStem() and isStubRoot(file.getParent()) + ) +} + +private predicate isStubRoot(Folder f) { + not f.getParent*().isImportRoot() and + f.getAbsolutePath().matches("%/data/python/stubs") +} + diff --git a/python/ql/src/semmle/python/Operations.qll b/python/ql/src/semmle/python/Operations.qll new file mode 100644 index 000000000000..3ea0287dac7b --- /dev/null +++ b/python/ql/src/semmle/python/Operations.qll @@ -0,0 +1,344 @@ +import python + +/** Base class for operators */ +class Operator extends Operator_ { + + /** Gets the name of the special method used to implement this operator */ + string getSpecialMethodName() { none() } + +} + +/* Unary Expression and its operators */ + +/** A unary expression: (`+x`), (`-x`) or (`~x`) */ +class UnaryExpr extends UnaryExpr_ { + + override Expr getASubExpression() { + result = this.getOperand() + } + +} + +/** A unary operator: `+`, `-`, `~` or `not` */ +class Unaryop extends Unaryop_ { + + /** Gets the name of the special method used to implement this operator */ + string getSpecialMethodName() { none() } + +} + +/** An invert (`~`) unary operator */ +class Invert extends Invert_ { + + override string getSpecialMethodName() { result = "__invert__" } + +} + +/** A positive (`+`) unary operator */ +class UAdd extends UAdd_ { + + override string getSpecialMethodName() { result = "__pos__" } + +} + +/** A negation (`-`) unary operator */ +class USub extends USub_ { + + override string getSpecialMethodName() { result = "__neg__" } + +} + +/** A `not` unary operator */ +class Not extends Not_ { + + override string getSpecialMethodName() { none() } + +} + + +/* Binary Operation and its operators */ + +/** A binary expression, such as `x + y` */ +class BinaryExpr extends BinaryExpr_ { + + override Expr getASubExpression() { + result = this.getLeft() or result = this.getRight() + } + +} + +/** A power (`**`) binary operator */ +class Pow extends Pow_ { + + override string getSpecialMethodName() { result = "__pow__" } + +} + +/** A right shift (`>>`) binary operator */ +class RShift extends RShift_ { + + override string getSpecialMethodName() { result = "__rshift__" } + +} + +/** A subtract (`-`) binary operator */ +class Sub extends Sub_ { + + override string getSpecialMethodName() { result = "__sub__" } + +} + +/** A bitwise and (`&`) binary operator */ +class BitAnd extends BitAnd_ { + + override string getSpecialMethodName() { result = "__and__" } + +} + +/** A bitwise or (`|`) binary operator */ +class BitOr extends BitOr_ { + + override string getSpecialMethodName() { result = "__or__" } + +} + +/** A bitwise exclusive-or (`^`) binary operator */ +class BitXor extends BitXor_ { + + override string getSpecialMethodName() { result = "__xor__" } + +} + +/** An add (`+`) binary operator */ +class Add extends Add_ { + + override string getSpecialMethodName() { result = "__add__" } +} + +/** An (true) divide (`/`) binary operator */ +class Div extends Div_ { + + override string getSpecialMethodName() { + result = "__truediv__" + or + major_version() = 2 and result = "__div__" + } +} + +/** An floor divide (`//`) binary operator */ +class FloorDiv extends FloorDiv_ { + + override string getSpecialMethodName() { result = "__floordiv__" } + +} + +/** A left shift (`<<`) binary operator */ +class LShift extends LShift_ { + + override string getSpecialMethodName() { result = "__lshift__" } + +} + +/** A modulo (`%`) binary operator, which includes string formatting */ +class Mod extends Mod_ { + + override string getSpecialMethodName() { result = "__mod__" } + +} + +/** A multiplication (`*`) binary operator */ +class Mult extends Mult_ { + + override string getSpecialMethodName() { result = "__mul__" } + +} + +/** A matrix multiplication (`@`) binary operator */ +class MatMult extends MatMult_ { + + override string getSpecialMethodName() { result = "__matmul__" } + +} + +/* Comparison Operation and its operators */ + +/** A comparison operation, such as `x`) comparison operator */ +class Gt extends Gt_ { + + override string getSymbol() { + result = ">" + } + + override string getSpecialMethodName() { result = "__gt__" } + +} + +/** A greater than or equals (`>=`) comparison operator */ +class GtE extends GtE_ { + + override string getSymbol() { + result = ">=" + } + + override string getSpecialMethodName() { result = "__ge__" } + +} + +/** An `in` comparison operator */ +class In extends In_ { + + override string getSymbol() { + result = "in" + } + +} + +/** An `is` comparison operator */ +class Is extends Is_ { + + override string getSymbol() { + result = "is" + } + +} + +/** An `is not` comparison operator */ +class IsNot extends IsNot_ { + + override string getSymbol() { + result = "is not" + } + +} + +/** An equals (`==`) comparison operator */ +class Eq extends Eq_ { + + override string getSymbol() { + result = "==" + } + + override string getSpecialMethodName() { result = "__eq__" } + +} + +/** A less than (`<`) comparison operator */ +class Lt extends Lt_ { + + override string getSymbol() { + result = "<" + } + + override string getSpecialMethodName() { result = "__lt__" } + +} + +/** A less than or equals (`<=`) comparison operator */ +class LtE extends LtE_ { + + override string getSymbol() { + result = "<=" + } + + override string getSpecialMethodName() { result = "__le__" } + +} + +/** A not equals (`!=`) comparison operator */ +class NotEq extends NotEq_ { + + override string getSymbol() { + result = "!=" + } + + override string getSpecialMethodName() { result = "__ne__" } + +} + +/** An `not in` comparison operator */ +class NotIn extends NotIn_ { + + override string getSymbol() { + result = "not in" + } + +} + +/* Boolean Operation (and/or) and its operators */ + +/** A boolean shortcut (and/or) operation */ +class BoolExpr extends BoolExpr_ { + + override Expr getASubExpression() { + result = this.getAValue() + } + + string getOperator() { + this.getOp() instanceof And and result = "and" + or + this.getOp() instanceof Or and result = "or" + } + + /** Whether part evaluates to partIsTrue if this evaluates to wholeIsTrue */ + predicate impliesValue(Expr part, boolean partIsTrue, boolean wholeIsTrue) { + if this.getOp() instanceof And then ( + wholeIsTrue = true and partIsTrue = true and part = this.getAValue() + or + wholeIsTrue = true and ((BoolExpr)this.getAValue()).impliesValue(part, partIsTrue, true) + ) else ( + wholeIsTrue = false and partIsTrue = false and part = this.getAValue() + or + wholeIsTrue = false and ((BoolExpr)this.getAValue()).impliesValue(part, partIsTrue, false) + ) + } + +} + +/** A short circuit boolean operator, and/or */ +class Boolop extends Boolop_ { + +} + +/** An `and` boolean operator */ +class And extends And_ { + +} + +/** An `or` boolean operator */ +class Or extends Or_ { + +} diff --git a/python/ql/src/semmle/python/SSA.qll b/python/ql/src/semmle/python/SSA.qll new file mode 100644 index 000000000000..0471a673bf4d --- /dev/null +++ b/python/ql/src/semmle/python/SSA.qll @@ -0,0 +1,240 @@ +/** SSA library */ + +import python + +/** A single static assignment variable. + * An SSA variable is a variable which is only assigned once (statically). + * SSA variables can be defined as normal variables or by a phi node which can occur at joins in the flow graph. + * Definitions without uses do not have a SSA variable. + */ +class SsaVariable extends @py_ssa_var{ + + SsaVariable() { + py_ssa_var(this, _) + } + + /** Gets the source variable */ + Variable getVariable() { + py_ssa_var(this, result) + } + + /** Gets a use of this variable */ + ControlFlowNode getAUse() { + py_ssa_use(result, this) + } + + /** Gets the definition (which may be a deletion) of this SSA variable */ + ControlFlowNode getDefinition() { + py_ssa_defn(this, result) + } + + /** Gets an argument of the phi function defining this variable. + * This predicate uses the raw SSA form produced by the extractor. + * In general, you should use `getAPrunedPhiInput()` instead. */ + SsaVariable getAPhiInput() { + py_ssa_phi(this, result) + } + + /** Gets the edge(s) (result->this.getDefinition()) on which the SSA variable 'input' defines this SSA variable. + * For each incoming edge `X->B`, where `B` is the basic block containing this phi-node, only one of the input SSA variables + * for this phi-node is live. This predicate returns the predecessor block such that the variable 'input' + * is the live variable on the edge result->B. + */ + BasicBlock getPredecessorBlockForPhiArgument(SsaVariable input) { + input = this.getAPhiInput() and + result = this.getAPredecessorBlockForPhi() and + input.getDefinition().getBasicBlock().dominates(result) and + /* Beware the case where an SSA variable that is an input on one edge dominates another edge. + * Consider (in SSA form): + * x0 = 0 + * if cond: + * x1 = 1 + * x2 = phi(x0, x1) + * use(x2) + * + * The definition of x0 dominates the exit from the block x1=1, even though it does not reach it. + * Hence we need to check that no other definition dominates the edge and actually reaches it. + * Note that if a dominates c and b dominates c, then either a dominates b or vice-versa. + */ + not exists(SsaVariable other, BasicBlock other_def | + not other = input and + other = this.getAPhiInput() and + other_def = other.getDefinition().getBasicBlock() + | + other_def.dominates(result) and + input.getDefinition().getBasicBlock().strictlyDominates(other_def) + ) + } + + /** Gets an argument of the phi function defining this variable, pruned of unlikely edges. */ + SsaVariable getAPrunedPhiInput() { + result = this.getAPhiInput() and + exists(BasicBlock incoming | incoming = this.getPredecessorBlockForPhiArgument(result) | + not incoming.getLastNode().(RaisingNode).unlikelySuccessor(this.getDefinition()) + ) + } + + /** Gets a variable that ultimately defines this variable and is not itself defined by another variable */ + SsaVariable getAnUltimateDefinition() { + result = this and not exists(this.getAPhiInput()) + or + result = this.getAPhiInput().getAnUltimateDefinition() + } + + string toString() { + result = "SSA Variable " + this.getId() + } + + Location getLocation() { + result = this.getDefinition().getLocation() + } + + /** Gets the id (name) of this variable */ + string getId() { + result = this.getVariable().getId() + } + + /** Gets the incoming edges for a Phi node. */ + private BasicBlock getAPredecessorBlockForPhi() { + exists(getAPhiInput()) and + result.getASuccessor() = this.getDefinition().getBasicBlock() + } + + /** Gets the incoming edges for a Phi node, pruned of unlikely edges. */ + private BasicBlock getAPrunedPredecessorBlockForPhi() { + result = this.getAPredecessorBlockForPhi() and + not result.unlikelySuccessor(this.getDefinition().getBasicBlock()) + } + + /** Whether it is possible to reach a use of this variable without passing a definition */ + predicate reachableWithoutDefinition() { + not exists(this.getDefinition()) and not py_ssa_phi(this, _) + or + exists(SsaVariable var | var = this.getAPhiInput() | var.reachableWithoutDefinition()) + or + /* For phi-nodes, there must be a corresponding phi-input for each control-flow + * predecessor. Otherwise, the variable will be undefined on that incoming edge. + * WARNING: the same phi-input may cover multiple predecessors, so this check + * cannot be done by counting. + */ + exists(BasicBlock incoming | + incoming = this.getAPredecessorBlockForPhi() and + not this.getAPhiInput().getDefinition().getBasicBlock().dominates(incoming) + ) + } + + /** Whether this variable may be undefined */ + predicate maybeUndefined() { + not exists(this.getDefinition()) and not py_ssa_phi(this, _) and not this.implicitlyDefined() + or + this.getDefinition().isDelete() + or + exists(SsaVariable var | var = this.getAPrunedPhiInput() | var.maybeUndefined()) + or + /* For phi-nodes, there must be a corresponding phi-input for each control-flow + * predecessor. Otherwise, the variable will be undefined on that incoming edge. + * WARNING: the same phi-input may cover multiple predecessors, so this check + * cannot be done by counting. + */ + exists(BasicBlock incoming | + reaches_end(incoming) and + incoming = this.getAPrunedPredecessorBlockForPhi() and + not this.getAPhiInput().getDefinition().getBasicBlock().dominates(incoming) + ) + } + + private predicate implicitlyDefined() { + not exists(this.getDefinition()) and not py_ssa_phi(this, _) and + exists(GlobalVariable var | this.getVariable() = var | + globallyDefinedName(var.getId()) or + var.getId() = "__path__" and ((Module)var.getScope()).isPackageInit() + ) + } + + /** Gets the global variable that is accessed if this local is undefined. + * Only applies to local variables in class scopes. + */ + GlobalVariable getFallbackGlobal() { + exists(LocalVariable local, Class cls | this.getVariable() = local | + local.getScope() = cls and + result.getScope() = cls.getScope() and + result.getId() = local.getId() and + not exists(this.getDefinition()) + ) + } + + /* Whether this SSA variable is the first parameter of a method + * (regardless of whether it is actually called self or not) + */ + predicate isSelf() { + exists(Function func | + func.isMethod() + and + this.getDefinition().getNode() = func.getArg(0) + ) + } +} + +private predicate reaches_end(BasicBlock b) { + not exits_early(b) + and + ( + /* Entry point */ + not exists(BasicBlock prev | prev.getASuccessor() = b) + or + exists(BasicBlock prev | prev.getASuccessor() = b | + reaches_end(prev) + ) + ) +} + +private predicate exits_early(BasicBlock b) { + exists(FunctionObject f | + f.neverReturns() and + f.getACall().getBasicBlock() = b + ) +} + +private predicate gettext_installed() { + // Good enough (and fast) approximation + exists(Module m | m.getName() = "gettext") +} + +private predicate builtin_constant(string name) { + exists(builtin_object(name)) + or + name = "WindowsError" + or + name = "_" and gettext_installed() +} + +private predicate auto_name(string name) { + name = "__file__" or name = "__builtins__" or name = "__name__" +} + +/** Whether this name is (almost) always defined, ie. it is a builtin or VM defined name */ +predicate globallyDefinedName(string name) { + builtin_constant(name) or auto_name(name) +} + +/** An SSA variable that is backed by a global variable */ +class GlobalSsaVariable extends EssaVariable { + + GlobalSsaVariable() { + this.getSourceVariable() instanceof GlobalVariable + } + + GlobalVariable getVariable() { + result = this.getSourceVariable() + } + + string getId() { + result = this.getVariable().getId() + } + + override string toString() { + result = "GSSA Variable " + this.getId() + } + + +} diff --git a/python/ql/src/semmle/python/Scope.qll b/python/ql/src/semmle/python/Scope.qll new file mode 100755 index 000000000000..54274623216f --- /dev/null +++ b/python/ql/src/semmle/python/Scope.qll @@ -0,0 +1,181 @@ +import python + +/** A Scope. A scope is the lexical extent over which all identifiers with the same name refer to the same variable. + * Modules, Classes and Functions are all Scopes. There are no other scopes. + * The scopes for expressions that create new scopes, lambdas and comprehensions, are handled by creating an anonymous Function. */ +class Scope extends Scope_ { + + Module getEnclosingModule() { + result = this.getEnclosingScope().getEnclosingModule() + } + + /** This method will be deprecated in the next release. Please use `getEnclosingScope()` instead. + * The reason for this is to avoid confusion around use of `x.getScope+()` where `x` might be an + * `AstNode` or a `Variable`. Forcing the users to write `x.getScope().getEnclosingScope*()` ensures that + * the apparent semantics and the actual semantics coincide. + * [ Gets the scope enclosing this scope (modules have no enclosing scope) ] + */ + Scope getScope() { + none() + } + + /** Gets the scope enclosing this scope (modules have no enclosing scope) */ + Scope getEnclosingScope() { + none() + } + + /** Gets the statements forming the body of this scope */ + StmtList getBody() { + none() + } + + /** Gets the nth statement of this scope */ + Stmt getStmt(int n) { + none() + } + + /** Gets a top-level statement in this scope */ + Stmt getAStmt() { + none() + } + + Location getLocation() { + none() + } + + /** Gets the name of this scope */ + string getName() { + py_strs(result, this, 0) + } + + /** Gets the docstring for this scope */ + StrConst getDocString() { + result = ((ExprStmt)this.getStmt(0)).getValue() + } + + /** Gets the entry point into this Scope's control flow graph */ + ControlFlowNode getEntryNode() { + py_scope_flow(result, this, -1) + } + + /** Gets the non-explicit exit from this Scope's control flow graph */ + ControlFlowNode getFallthroughNode() { + py_scope_flow(result, this, 0) + } + + /** Gets the exit of this scope following from a return statement */ + ControlFlowNode getReturnNode() { + py_scope_flow(result, this, 2) + } + + /** Gets an exit from this Scope's control flow graph */ + ControlFlowNode getAnExitNode() { + exists (int i | py_scope_flow(result, this, i) and i >= 0) + } + + /** Gets an exit from this Scope's control flow graph, + * that does not result from an exception */ + ControlFlowNode getANormalExit() { + result = this.getFallthroughNode() + or + result = this.getReturnNode() + } + + /** Holds if this a top-level (non-nested) class or function */ + predicate isTopLevel() { + this.getEnclosingModule() = this.getEnclosingScope() + } + + /** Holds if this scope is deemed to be public */ + predicate isPublic() { + /* Not inside a function */ + not this.getEnclosingScope() instanceof Function and + /* Not implicitly private */ + this.getName().charAt(0) != "_" and + ( + this instanceof Module + or + exists(Module m | + m = this.getEnclosingScope() and m.isPublic() | + /* If the module has an __all__, is this in it */ + not exists(m.getAnExport()) + or + m.getAnExport() = this.getName() + ) + or + exists(Class c | c = this.getEnclosingScope() | + this instanceof Function and + c.isPublic() + ) + ) + } + + predicate contains(AstNode a) { + this.getBody().contains(a) + or + exists(Scope inner | inner.getEnclosingScope() = this | inner.contains(a)) + } + + /** Holds if this scope can be expected to execute before `other`. + * Modules precede functions and methods in those modules + * `__init__` precedes other methods. `__enter__` precedes `__exit__`. + * NOTE that this is context-insensitive, so a module "precedes" a function + * in that module, even if that function is called from the module scope. + */ + predicate precedes(Scope other) { + exists(Function f, string name | + f = other and name = f.getName() | + if f.isMethod() then ( + // The __init__ method is preceded by the enclosing module + this = f.getEnclosingModule() and name = "__init__" + or + exists(Class c, string pred_name | + // __init__ -> __enter__ -> __exit__ + // __init__ -> other-methods + f.getScope() = c and ( + pred_name = "__init__" and not name = "__init__" and not name = "__exit__" + or + pred_name = "__enter__" and name = "__exit__" + ) + | + this.getScope() = c and + pred_name = this.(Function).getName() + or + not exists(Function pre_func | + pre_func.getName() = pred_name and + pre_func.getScope() = c + ) and this = other.getEnclosingModule() + ) + ) else ( + // Normal functions are preceded by the enclosing module + this = f.getEnclosingModule() + ) + ) + } + + /** Gets the evaluation scope for code in this (lexical) scope. + * This is usually the scope itself, but may be an enclosing scope. + * Notably, for list comprehensions in Python 2. + */ + Scope getEvaluatingScope() { + result = this + } + + /** Holds if this scope is in the source archive, + * that is it is part of the code specified, not library code + */ + predicate inSource() { + exists(this.getEnclosingModule().getFile().getRelativePath()) + } + + Stmt getLastStatement() { + result = this.getBody().getLastItem().getLastStatement() + } + + /** Whether this contains `inner` syntactically and `inner` has the same scope as `this` */ + predicate containsInScope(AstNode inner) { + this.getBody().contains(inner) and + this = inner.getScope() + } + +} diff --git a/python/ql/src/semmle/python/SelfAttribute.qll b/python/ql/src/semmle/python/SelfAttribute.qll new file mode 100644 index 000000000000..9e165a4bb54b --- /dev/null +++ b/python/ql/src/semmle/python/SelfAttribute.qll @@ -0,0 +1,241 @@ +/** Utilities to support queries about instance attribute accesses of + * the form `self.attr`. + */ + +import python +private import semmle.python.pointsto.PointsTo +private import semmle.python.pointsto.Filters + +/** An attribute access where the left hand side of the attribute expression + * is `self`. + */ +class SelfAttribute extends Attribute { + + SelfAttribute() { + self_attribute(this, _) + } + + Class getClass() { + self_attribute(this, result) + } + +} + +/** Whether variable 'self' is the self variable in method 'method' */ +private predicate self_variable(Function method, Variable self) { + self.isParameter() and + method.isMethod() and + method.getArg(0).asName() = self.getAnAccess() +} + +/** Whether attribute is an access of the form `self.attr` in the body of the class 'cls' */ +private predicate self_attribute(Attribute attr, Class cls) { + exists(Function f, Variable self | + self_variable(f, self) | + self.getAnAccess() = attr.getObject() and + cls = f.getScope+() + ) +} + +/** Helper class for UndefinedClassAttribute.ql & MaybeUndefinedClassAttribute.ql */ +class SelfAttributeRead extends SelfAttribute { + + SelfAttributeRead() { + this.getCtx() instanceof Load and + /* Be stricter for loads. + * We want to generous as to what is defined (ie stores), + * but strict as to what needs to be defined (ie loads). + */ + exists(ClassObject cls, FunctionObject func | + cls.declaredAttribute(_) = func | + func.getFunction() = this.getScope() and + cls.getPyClass() = this.getClass() + ) + } + + predicate guardedByHasattr() { + exists(Variable var, ControlFlowNode n | + var.getAUse() = this.getObject().getAFlowNode() and + hasattr(n, var.getAUse(), this.getName()) and + n.strictlyDominates(this.getAFlowNode()) + ) + } + + pragma [noinline] predicate locallyDefined() { + exists(SelfAttributeStore store | + this.getName() = store.getName() and + this.getScope() = store.getScope() | + store.getAFlowNode().strictlyDominates(this.getAFlowNode()) + ) + } + +} + +class SelfAttributeStore extends SelfAttribute { + + SelfAttributeStore() { + this.getCtx() instanceof Store + } + + Expr getAssignedValue() { + exists(Assign a | a.getATarget() = this | + result = a.getValue() + ) + } + +} + +private Object object_getattribute() { + py_cmembers_versioned(theObjectType(), "__getattribute__", result, major_version().toString()) +} + +/** Helper class for UndefinedClassAttribute.ql and MaybeUndefinedClassAttribute.ql */ +class CheckClass extends ClassObject { + + private predicate ofInterest() { + not this.unknowableAttributes() and + not this.getPyClass().isProbableMixin() and + this.getPyClass().isPublic() and + not this.getPyClass().getScope() instanceof Function and + not this.probablyAbstract() and + not this.declaresAttribute("__new__") and + not this.selfDictAssigns() and + not this.lookupAttribute("__getattribute__") != object_getattribute() and + not this.hasAttribute("__getattr__") and + not this.selfSetattr() and + /* If class overrides object.__init__, but we can't resolve it to a Python function then give up */ + forall(ClassObject sup | + sup = this.getAnImproperSuperType() and + sup.declaresAttribute("__init__") and + not sup = theObjectType() | + sup.declaredAttribute("__init__") instanceof PyFunctionObject + ) + } + + predicate alwaysDefines(string name) { + auto_name(name) or + this.hasAttribute(name) or + this.getAnImproperSuperType().assignedInInit(name) or + this.getMetaClass().assignedInInit(name) + } + + predicate sometimesDefines(string name) { + this.alwaysDefines(name) or + exists(SelfAttributeStore sa | + sa.getScope().getScope+() = this.getAnImproperSuperType().getPyClass() | + name = sa.getName() + ) + } + + private predicate selfDictAssigns() { + exists(Assign a, SelfAttributeRead self_dict, Subscript sub | + self_dict.getName() = "__dict__" and + ( + self_dict = sub.getObject() + or + /* Indirect assignment via temporary variable */ + exists(SsaVariable v | + v.getAUse() = sub.getObject().getAFlowNode() and + v.getDefinition().(DefinitionNode).getValue() = self_dict.getAFlowNode() + ) + ) and + a.getATarget() = sub and + exists(FunctionObject meth | meth = this.lookupAttribute(_) and a.getScope() = meth.getFunction()) + ) + } + + pragma [nomagic] + private predicate monkeyPatched(string name) { + exists(Attribute a | + a.getCtx() instanceof Store and + PointsTo::points_to(a.getObject().getAFlowNode(), _, this, _, _) and a.getName() = name + ) + } + + private predicate selfSetattr() { + exists(Call c, Name setattr, Name self, Function method | + ( method.getScope() = this.getPyClass() or + method.getScope() = this.getASuperType().getPyClass() + ) and + c.getScope() = method and + c.getFunc() = setattr and + setattr.getId() = "setattr" and + c.getArg(0) = self and + self.getId() = "self" + ) + } + + predicate interestingUndefined(SelfAttributeRead a) { + exists(string name | name = a.getName() | + interestingContext(a, name) and + not this.definedInBlock(a.getAFlowNode().getBasicBlock(), name) + ) + } + + private predicate interestingContext(SelfAttributeRead a, string name) { + name = a.getName() and + this.ofInterest() and + this.getPyClass() = a.getScope().getScope() and + not a.locallyDefined() and + not a.guardedByHasattr() and + a.getScope().isPublic() and + not this.monkeyPatched(name) and + not attribute_assigned_in_method(lookupAttribute("setUp"), name) + } + + private predicate probablyAbstract() { + this.getName().matches("Abstract%") + or + this.isAbstract() + } + + private pragma[nomagic] predicate definitionInBlock(BasicBlock b, string name) { + exists(SelfAttributeStore sa | + sa.getAFlowNode().getBasicBlock() = b and sa.getName() = name and sa.getClass() = this.getPyClass() + ) + or + exists(FunctionObject method | this.lookupAttribute(_) = method | + attribute_assigned_in_method(method, name) and + b = method.getACall().getBasicBlock() + ) + } + + private pragma[nomagic] predicate definedInBlock(BasicBlock b, string name) { + // manual specialisation: this is only called from interestingUndefined, + // so we can push the context in from there, which must apply to a + // SelfAttributeRead in the same scope + exists(SelfAttributeRead a | + a.getScope() = b.getScope() and name = a.getName() | + interestingContext(a, name) + ) + and + this.definitionInBlock(b, name) + or + exists(BasicBlock prev | this.definedInBlock(prev, name) and prev.getASuccessor() = b) + } + +} + +private predicate attr_assigned_in_method_arg_n(FunctionObject method, string name, int n) { + exists(SsaVariable param | + method.getFunction().getArg(n).asName() = param.getDefinition().getNode() + | + exists(AttrNode attr | + attr.getObject(name) = param.getAUse() and + attr.isStore() + ) + or + exists(CallNode call, FunctionObject callee, int m | + callee.getArgumentForCall(call, m) = param.getAUse() and + attr_assigned_in_method_arg_n(callee, name, m) + ) + ) +} + +predicate attribute_assigned_in_method(FunctionObject method, string name) { + attr_assigned_in_method_arg_n(method, name, 0) +} + +private predicate auto_name(string name) { + name = "__class__" or name = "__dict__" +} diff --git a/python/ql/src/semmle/python/Stmts.qll b/python/ql/src/semmle/python/Stmts.qll new file mode 100644 index 000000000000..b6e0a2b79451 --- /dev/null +++ b/python/ql/src/semmle/python/Stmts.qll @@ -0,0 +1,546 @@ +import python + +/** A statement */ +class Stmt extends Stmt_, AstNode { + + /** Gets the scope immediately enclosing this statement */ + override Scope getScope() { + py_scopes(this, result) + } + + override string toString() { + result = "Stmt" + } + + /** Gets the module enclosing this statement */ + Module getEnclosingModule() { + result = this.getScope().getEnclosingModule() + } + + override Location getLocation() { + result = Stmt_.super.getLocation() + } + + /** Gets an immediate (non-nested) sub-expression of this statement */ + Expr getASubExpression() { + none() + } + + /** Gets an immediate (non-nested) sub-statement of this statement */ + Stmt getASubStatement() { + none() + } + + override AstNode getAChildNode() { + result = this.getASubExpression() + or + result = this.getASubStatement() + } + + private ControlFlowNode possibleEntryNode() { + result.getNode() = this or + this.containsInScope(result.getNode()) + } + + + /** Gets a control flow node for an entry into this statement. + */ + ControlFlowNode getAnEntryNode() { + result = this.possibleEntryNode() and + exists(ControlFlowNode pred | + pred.getASuccessor() = result and + not pred = this.possibleEntryNode() + ) + } + + /** Holds if this statement cannot be reached */ + predicate isUnreachable() { + not exists(this.getAnEntryNode()) + or + exists(If ifstmt | + ifstmt.getTest().(ImmutableLiteral).booleanValue() = false and ifstmt.getBody().contains(this) + or + ifstmt.getTest().(ImmutableLiteral).booleanValue() = true and ifstmt.getOrelse().contains(this) + ) + or + exists(While whilestmt | + whilestmt.getTest().(ImmutableLiteral).booleanValue() = false and whilestmt.getBody().contains(this) + ) + } + + /** Gets the final statement in this statement, ordered by location. + * Will be this statement if not a compound statement. + */ + Stmt getLastStatement() { + result = this + } + +} + +/** A statement that includes a binding (except imports) */ +class Assign extends Assign_ { + + /** Use ControlFlowNodes and SsaVariables for data-flow analysis. */ + predicate defines(Variable v) { + this.getATarget().defines(v) + } + + override Expr getASubExpression() { + result = this.getATarget() or + result = this.getValue() + } + + override Stmt getASubStatement() { + none() + } +} + +/** An augmented assignment statement, such as `x += y` */ +class AugAssign extends AugAssign_ { + + override Expr getASubExpression() { + result = this.getOperation() + } + + Expr getTarget() { + result = ((BinaryExpr)this.getOperation()).getLeft() + } + + Expr getValue() { + result = ((BinaryExpr)this.getOperation()).getRight() + } + + override Stmt getASubStatement() { + none() + } +} + +/** An annotated assignment statement, such as `x: int = 0` */ +class AnnAssign extends AnnAssign_ { + + override Expr getASubExpression() { + result = this.getAnnotation() or + result = this.getTarget() or + result = this.getValue() + } + + override Stmt getASubStatement() { + none() + } + + /** Holds if the value of the annotation of this assignment is stored at runtime. */ + predicate isStored() { + not this.getScope() instanceof Function + and + exists(Name n | + n = this.getTarget() + and + not n.isParenthesized() + ) + } + +} + +/** An exec statement */ +class Exec extends Exec_ { + + override Expr getASubExpression() { + result = this.getBody() or + result = this.getGlobals() or + result = this.getLocals() + } + + override Stmt getASubStatement() { + none() + } + +} + +/** An except statement (part of a `try` statement), such as `except IOError as err:` */ +class ExceptStmt extends ExceptStmt_ { + + /** Gets the immediately enclosing try statement */ + Try getTry() { + result.getAHandler() = this + } + + override Expr getASubExpression() { + result = this.getName() + or + result = this.getType() + } + + override Stmt getASubStatement() { + result = this.getAStmt() + } + + override Stmt getLastStatement() { + result = this.getBody().getLastItem().getLastStatement() + } + +} + +/** An assert statement, such as `assert a == b, "A is not equal to b"` */ +class Assert extends Assert_ { + + override Expr getASubExpression() { + result = this.getMsg() or result = this.getTest() + } + + override Stmt getASubStatement() { + none() + } + +} + +/** A break statement */ +class Break extends Break_ { + + override Expr getASubExpression() { + none() + } + + override Stmt getASubStatement() { + none() + } + +} + +/** A continue statement */ +class Continue extends Continue_ { + + override Expr getASubExpression() { + none() + } + + override Stmt getASubStatement() { + none() + } + +} + +/** A delete statement, such as `del x[-1]` */ +class Delete extends Delete_ { + + override Expr getASubExpression() { + result = this.getATarget() + } + + override Stmt getASubStatement() { + none() + } + +} + +/** An expression statement, such as `len(x)` or `yield y` */ +class ExprStmt extends ExprStmt_ { + + override Expr getASubExpression() { + result = this.getValue() + } + + override Stmt getASubStatement() { + none() + } + +} + +/** A for statement, such as `for x in y: print(x)` */ +class For extends For_ { + + override Stmt getASubStatement() { + result = this.getAStmt() or + result = this.getAnOrelse() + } + + override Expr getASubExpression() { + result = this.getTarget() or + result = this.getIter() + } + + override Stmt getLastStatement() { + result = this.getBody().getLastItem().getLastStatement() + } + +} + +/** A global statement, such as `global var` */ +class Global extends Global_ { + + override Expr getASubExpression() { + none() + } + + override Stmt getASubStatement() { + none() + } +} + +/** An if statement, such as `if eggs: print("spam")` */ +class If extends If_ { + + override Stmt getASubStatement() { + result = this.getAStmt() or + result = this.getAnOrelse() + } + + override Expr getASubExpression() { + result = this.getTest() + } + + /** Whether this if statement takes the form `if __name__ == "__main__":` */ + predicate isNameEqMain() { + exists(StrConst m, Name n, Compare c | + this.getTest() = c and + c.getOp(0) instanceof Eq and + ( + c.getLeft() = n and c.getComparator(0) = m + or + c.getLeft() = m and c.getComparator(0) = n + ) and + n.getId() = "__name__" and + m.getText() = "__main__" + ) + } + + /** Whether this if statement starts with the keyword `elif` */ + predicate isElif() { + /* The Python parser turns all elif chains into nested if-else statements. + * An `elif` can be identified as it is the first statement in an `else` block + * and it is not indented relative to its parent `if`. + */ + exists(If i | + i.getOrelse(0) = this and + this.getLocation().getStartColumn() = i.getLocation().getStartColumn() + ) + } + + /** Gets the `elif` branch of this `if`-statement, if present */ + If getElif() { + result = this.getOrelse(0) and + result.isElif() + } + + override Stmt getLastStatement() { + result = this.getOrelse().getLastItem().getLastStatement() + or + not exists(this.getOrelse()) and + result = this.getBody().getLastItem().getLastStatement() + } + +} + +/** A nonlocal statement, such as `nonlocal var` */ +class Nonlocal extends Nonlocal_ { + + override Stmt getASubStatement() { + none() + } + + override Expr getASubExpression() { + none() + } + + Variable getAVariable() { + result.getScope() = this.getScope() and + result.getId() = this.getAName() + } + +} + +/** A pass statement */ +class Pass extends Pass_ { + + override Stmt getASubStatement() { + none() + } + + override Expr getASubExpression() { + none() + } + +} + +/** A print statement (Python 2 only), such as `print 0` */ +class Print extends Print_ { + + override Stmt getASubStatement() { + none() + } + + override Expr getASubExpression() { + result = this.getAValue() or + result = this.getDest() + } + +} + +/** A raise statement, such as `raise CompletelyDifferentException()` */ +class Raise extends Raise_ { + + override Stmt getASubStatement() { + none() + } + + override Expr getASubExpression() { + py_exprs(result, _, this, _) + } + + /** The expression immediately following the `raise`, this is the + * exception raised, but not accounting for tuples in Python 2. + */ + Expr getException() { + result = this.getType() + or + result = this.getExc() + } + + /** The exception raised, accounting for tuples in Python 2. */ + Expr getRaised() + { + exists(Expr raw | + raw = this.getException() | + if (not major_version() = 2 or not exists(raw.(Tuple).getAnElt())) then + result = raw + else + /* In Python 2 raising a tuple will result in the first element of the tuple being raised. */ + result = raw.(Tuple).getElt(0) + ) + } +} + +/** A return statement, such as return None */ +class Return extends Return_ { + + override Stmt getASubStatement() { + none() + } + + override Expr getASubExpression() { + result = this.getValue() + } + +} + +/** A try statement */ +class Try extends Try_ { + + override Expr getASubExpression() { + none() + } + + override Stmt getASubStatement() { + result = this.getAHandler() or + result = this.getAStmt() or + result = this.getAFinalstmt() or + result = this.getAnOrelse() + } + + override ExceptStmt getHandler(int i) { + result = Try_.super.getHandler(i) + } + + /** Gets an exception handler of this try statement. */ + override ExceptStmt getAHandler() { + result = Try_.super.getAHandler() + } + + override Stmt getLastStatement() { + result = this.getFinalbody().getLastItem().getLastStatement() + or + not exists(this.getFinalbody()) and + result = this.getOrelse().getLastItem().getLastStatement() + or + not exists(this.getFinalbody()) and not exists(this.getOrelse()) and + result = this.getHandlers().getLastItem().getLastStatement() + or + not exists(this.getFinalbody()) and not exists(this.getOrelse()) and not exists(this.getHandlers()) and + result = this.getBody().getLastItem().getLastStatement() + } + +} + +/** A while statement, such as `while parrot_resting():` */ +class While extends While_ { + + override Expr getASubExpression() { + result = this.getTest() + } + + override Stmt getASubStatement() { + result = this.getAStmt() or + result = this.getAnOrelse() + } + + override Stmt getLastStatement() { + result = this.getOrelse().getLastItem().getLastStatement() + or + not exists(this.getOrelse()) and + result = this.getBody().getLastItem().getLastStatement() + } + +} + +/** A with statement such as `with f as open("file"): text = f.read()` */ +class With extends With_ { + + override Expr getASubExpression() { + result = this.getContextExpr() or + result = this.getOptionalVars() + } + + override Stmt getASubStatement() { + result = this.getAStmt() + } + + override Stmt getLastStatement() { + result = this.getBody().getLastItem().getLastStatement() + } + +} + +/** A plain text used in a template is wrapped in a TemplateWrite statement */ +class TemplateWrite extends TemplateWrite_ { + + override Expr getASubExpression() { + result = this.getValue() + } + + override Stmt getASubStatement() { + none() + } + +} + +class AsyncFor extends For { + + AsyncFor() { + this.isAsync() + } + +} + +class AsyncWith extends With { + + AsyncWith() { + this.isAsync() + } + +} + +/** A list of statements */ +class StmtList extends StmtList_ { + + /** Whether this list of statements contains s */ + predicate contains(AstNode a) { + exists(Stmt item | + item = this.getAnItem() | + item = a or item.contains(a) + ) + } + + Stmt getLastItem() { result = this.getItem(max(int i | exists(this.getItem(i)))) } + +} + + diff --git a/python/ql/src/semmle/python/TestUtils.qll b/python/ql/src/semmle/python/TestUtils.qll new file mode 100644 index 000000000000..6697dbed00d6 --- /dev/null +++ b/python/ql/src/semmle/python/TestUtils.qll @@ -0,0 +1,24 @@ +/* This file contains test-related utility functions */ + +import python + + +/** Removes everything up to the occurrence of `sub` in the string `str` */ + +bindingset[str,sub] +string remove_prefix_before_substring(string str, string sub) { + exists(int index | + index = str.indexOf(sub) and + result = str.suffix(index) + ) + or + not exists(str.indexOf(sub)) and + result = str +} + +/** Removes the part of the `resources/lib` Python library path that may vary + * from machine to machine. */ + +string remove_library_prefix(Location loc) { + result = remove_prefix_before_substring(loc.toString(), "resources/lib") +} diff --git a/python/ql/src/semmle/python/Variables.qll b/python/ql/src/semmle/python/Variables.qll new file mode 100644 index 000000000000..21ccc43b545d --- /dev/null +++ b/python/ql/src/semmle/python/Variables.qll @@ -0,0 +1,123 @@ + +import python + +/** A variable, either a global or local variable (including parameters) */ +class Variable extends @py_variable { + + /** Gets the identifier (name) of this variable */ + string getId() { + variable(this, _, result) + } + + string toString() { + result = "Variable " + this.getId() + } + + /** Gets an access (load or store) of this variable */ + Name getAnAccess() { + result = this.getALoad() + or + result = this.getAStore() + } + + /** Gets a load of this variable */ + Name getALoad() { + result.uses(this) + } + + /** Gets a store of this variable */ + Name getAStore() { + result.defines(this) + } + + /** Gets a use of this variable */ + NameNode getAUse() { + result.uses(this) + } + + /** Gets the scope of this variable */ + Scope getScope() { + variable(this, result, _) + } + + /** Whether there is an access to this variable outside + * of its own scope. Usually occurs in nested functions + * or for global variables. + */ + predicate escapes() { + exists(Name n | n = this.getAnAccess() | n.getScope() != this.getScope()) + } + + /** Whether this variable is a parameter */ + predicate isParameter() { + none() + } + + predicate isSelf() { + none() + } + +} + +/** A local (function or class) variable */ +class LocalVariable extends Variable { + + LocalVariable() { + exists(Scope s | s = this.getScope() | s instanceof Function or s instanceof Class) + } + + override string toString() { + result = "Local Variable " + this.getId() + } + + /** Whether this variable is a parameter */ + override predicate isParameter() { + exists(Parameter p | this.getAnAccess() = p) + } + + /** Holds if this variable is the first parameter of a method. It is not necessarily called "self" */ + override predicate isSelf() { + exists(Function f, Parameter self | + this.getAnAccess() = self and + f.isMethod() and f.getArg(0) = self + ) + } + +} + +/** A local variable that uses "load fast" semantics, for lookup: + * If the variable is undefined, then raise an exception. + */ +class FastLocalVariable extends LocalVariable { + + FastLocalVariable() { + this.getScope() instanceof FastLocalsFunction + } + +} + +/** A local variable that uses "load name" semantics, for lookup: + * If the variable is undefined, then lookup the value in globals(). + */ +class NameLocalVariable extends LocalVariable { + + NameLocalVariable() { + not this instanceof FastLocalVariable + } + +} + +/** A global (module-level) variable */ +class GlobalVariable extends Variable { + + GlobalVariable() { + exists(Module m | m = this.getScope()) + } + + override string toString() { + result = "Global Variable " + this.getId() + } + +} + + diff --git a/python/ql/src/semmle/python/dataflow/SsaDefinitions.qll b/python/ql/src/semmle/python/dataflow/SsaDefinitions.qll new file mode 100644 index 000000000000..d6d499490535 --- /dev/null +++ b/python/ql/src/semmle/python/dataflow/SsaDefinitions.qll @@ -0,0 +1,463 @@ +/** Provides classes and predicates for determining the uses and definitions of + * variables for ESSA form. + */ + +import python +private import semmle.python.pointsto.Base + + +/* Classification of variables. These should be non-overlapping and complete. + * + * Function local variables - Non escaping variables in a function, except 'self' + * Self variables - The 'self' variable for a method. + * Class local variables - Local variables declared in a class + * Non-local variables - Escaping variables in a function + * Built-in variables - Global variables with no definition + * Non-escaping globals -- Global variables that have definitions and all of those definitions are in the module scope + * Escaping globals -- Global variables that have definitions and at least one of those definitions is in another scope. + */ + +/** Python specific version of `SsaSourceVariable`. */ +abstract class PythonSsaSourceVariable extends SsaSourceVariable { + + PythonSsaSourceVariable() { + /* Exclude `True`, `False` and `None` */ + not this.(Variable).getALoad() instanceof NameConstant + } + + override string getName() { + result = this.(Variable).getId() + } + + abstract ControlFlowNode getAnImplicitUse(); + + abstract ControlFlowNode getScopeEntryDefinition(); + + override ControlFlowNode getAUse() { + result = this.getASourceUse() + or + result = this.getAnImplicitUse() + or + /* `import *` is a definition of *all* variables, so must be a use as well, for pass-through + * once we have established that a variable is not redefined. + */ + SsaSource::import_star_refinement(this, result, _) + or + /* Add a use at the end of scope for all variables to keep them live + * This is necessary for taint-tracking. + */ + result = this.(Variable).getScope().getANormalExit() + } + + override predicate hasDefiningNode(ControlFlowNode def) { + def = this.getScopeEntryDefinition() + or + SsaSource::assignment_definition(this, def, _) + or + SsaSource::multi_assignment_definition(this, def) + or + SsaSource::deletion_definition(this, def) + or + SsaSource::iteration_defined_variable(this, def, _) + or + SsaSource::init_module_submodule_defn(this, def) + or + SsaSource::parameter_definition(this, def) + or + SsaSource::exception_capture(this, def) + or + SsaSource::with_definition(this, def) + } + + override predicate hasDefiningEdge(BasicBlock pred, BasicBlock succ) { + none() + } + + override predicate hasRefinement(ControlFlowNode use, ControlFlowNode def) { + this.hasDefiningNode(_) and /* Can't have a refinement unless there is a definition */ + refinement(this, use, def) + } + + override predicate hasRefinementEdge(ControlFlowNode use, BasicBlock pred, BasicBlock succ) { + use.(NameNode).uses(this) and + exists(ControlFlowNode test | + test.getAChild*() = use and + test.isBranch() and + test = pred.getLastNode() + ) and + (pred.getAFalseSuccessor() = succ or pred.getATrueSuccessor() = succ) + and + /* There is a store to this variable -- We don't want to refine builtins */ + exists(this.(Variable).getAStore()) and + /* There is at least one use or definition of the variable that is reachable by the test */ + exists(ControlFlowNode n | + n = this.getAUse() or + this.hasDefiningNode(n) | + pred.(ConditionBlock).strictlyReaches(n.getBasicBlock()) + ) + } + + override ControlFlowNode getASourceUse() { + result.(NameNode).uses(this) + or + result.(NameNode).deletes(this) + } + + abstract CallNode redefinedAtCallSite(); + +} + + +class FunctionLocalVariable extends PythonSsaSourceVariable { + + FunctionLocalVariable() { + this.(LocalVariable).getScope() instanceof Function and not this.(LocalVariable).escapes() + } + + override ControlFlowNode getAnImplicitUse() { + this.(Variable).isSelf() and this.(Variable).getScope().getANormalExit() = result + } + + override ControlFlowNode getScopeEntryDefinition() { + not this.(LocalVariable).getId() = "*" and + not this.(LocalVariable).isParameter() and + this.(LocalVariable).getScope().getEntryNode() = result + } + + override CallNode redefinedAtCallSite() { none() } + +} + +class NonLocalVariable extends PythonSsaSourceVariable { + + NonLocalVariable() { + this.(LocalVariable).getScope() instanceof Function and this.(LocalVariable).escapes() + } + + override ControlFlowNode getAnImplicitUse() { + result.(CallNode).getScope().getScope*() = this.(LocalVariable).getScope() + } + + override ControlFlowNode getScopeEntryDefinition() { + exists(Function f | + f.getScope+() = this.(LocalVariable).getScope() and + f.getEntryNode() = result + ) + or + not this.(LocalVariable).isParameter() and + this.(LocalVariable).getScope().getEntryNode() = result + } + + override CallNode redefinedAtCallSite() { + not this.(LocalVariable).getId() = "*" and + result.getScope().getScope*() = this.(LocalVariable).getScope() + } + +} + +class ClassLocalVariable extends PythonSsaSourceVariable { + + ClassLocalVariable() { + this.(LocalVariable).getScope() instanceof Class + } + + override ControlFlowNode getAnImplicitUse() { + none() + } + + override ControlFlowNode getScopeEntryDefinition() { + not this.(LocalVariable).getId() = "*" and + result = this.(LocalVariable).getScope().getEntryNode() + } + + override CallNode redefinedAtCallSite() { none() } + +} + +class BuiltinVariable extends PythonSsaSourceVariable { + + BuiltinVariable() { + this instanceof GlobalVariable and + not exists(this.(Variable).getAStore()) and + not this.(Variable).getId() = "__name__" and + not this.(Variable).getId() = "__package__" and + not exists(ImportStar is | is.getScope() = this.(Variable).getScope()) + } + + override ControlFlowNode getAnImplicitUse() { + none() + } + + override ControlFlowNode getScopeEntryDefinition() { + none() + } + + override CallNode redefinedAtCallSite() { none() } + +} + +class ModuleVariable extends PythonSsaSourceVariable { + + ModuleVariable() { + this instanceof GlobalVariable and + ( + exists(this.(Variable).getAStore()) + or + this.(Variable).getId() = "__name__" + or + this.(Variable).getId() = "__package__" + or + exists(ImportStar is | is.getScope() = this.(Variable).getScope()) + ) + } + + override ControlFlowNode getAnImplicitUse() { + result.getScope() = this.(GlobalVariable).getScope() and + ( + result instanceof CallNode + or + import_from_dot_in_init(result.(ImportMemberNode).getModule(this.getName())) + ) + or + exists(ImportTimeScope scope | + scope.entryEdge(result, _) | + this = scope.getOuterVariable(_) or + this.(Variable).getAUse().getScope() = scope + ) + or + /* For implicit use of __metaclass__ when constructing class */ + exists(Class c | + class_with_global_metaclass(c, this) and + c.(ImportTimeScope).entryEdge(result, _) + ) + or + exists(ImportTimeScope s | + result = s.getANormalExit() and this.(Variable).getScope() = s and + implicit_definition(this) + ) + } + + override ControlFlowNode getScopeEntryDefinition() { + not this.(GlobalVariable).getId() = "*" and + exists(Scope s | + s.getEntryNode() = result | + /* Module entry point */ + this.(GlobalVariable).getScope() = s + or + /* For implicit use of __metaclass__ when constructing class */ + class_with_global_metaclass(s, this) + or + /* Variable is used in scope */ + this.(GlobalVariable).getAUse().getScope() = s + ) + or + exists(ImportTimeScope scope | + scope.entryEdge(_, result) | + this = scope.getOuterVariable(_) or + this.(Variable).getAUse().getScope() = scope + ) + or + this.(GlobalVariable).getId() = "*" and + exists(Scope s | + s.getEntryNode() = result and + this.(Variable).getScope() = s and + exists(ImportStar is | is.getScope() = s) + ) + } + + override CallNode redefinedAtCallSite() { none() } + +} + +class NonEscapingGlobalVariable extends ModuleVariable { + + NonEscapingGlobalVariable() { + this instanceof GlobalVariable and + exists(this.(Variable).getAStore()) and + not variable_or_attribute_defined_out_of_scope(this) + } + +} + +class EscapingGlobalVariable extends ModuleVariable { + + EscapingGlobalVariable() { + this instanceof GlobalVariable and exists(this.(Variable).getAStore()) and variable_or_attribute_defined_out_of_scope(this) + } + + override ControlFlowNode getAnImplicitUse() { + result = ModuleVariable.super.getAnImplicitUse() + or + result.(CallNode).getScope().getScope+() = this.(GlobalVariable).getScope() + or + result = this.innerScope().getANormalExit() + } + + private Scope innerScope() { + result.getScope+() = this.(GlobalVariable).getScope() and + not result instanceof ImportTimeScope + } + + override ControlFlowNode getScopeEntryDefinition() { + result = ModuleVariable.super.getScopeEntryDefinition() + or + result = this.innerScope().getEntryNode() + } + + override CallNode redefinedAtCallSite() { + result.(CallNode).getScope().getScope*() = this.(GlobalVariable).getScope() + } + +} + +private predicate variable_or_attribute_defined_out_of_scope(Variable v) { + exists(NameNode n | n.defines(v) and not n.getScope() = v.getScope()) + or + exists(AttrNode a | a.isStore() and a.getObject() = v.getAUse() and not a.getScope() = v.getScope()) +} + +private predicate class_with_global_metaclass(Class cls, GlobalVariable metaclass) { + metaclass.getId() = "__metaclass__" and major_version() = 2 and + cls.getEnclosingModule() = metaclass.getScope() +} + + +/** Holds if this variable is implicitly defined */ +private predicate implicit_definition(Variable v) { + v.getId() = "*" + or + exists(ImportStar is | is.getScope() = v.getScope()) +} + +cached module SsaSource { + + /** Holds if `v` is used as the receiver in a method call. */ + cached predicate method_call_refinement(Variable v, ControlFlowNode use, CallNode call) { + use = v.getAUse() and + call.getFunction().(AttrNode).getObject() = use + } + + /** Holds if `v` is defined by assignment at `defn` and given `value`. */ + cached predicate assignment_definition(Variable v, ControlFlowNode defn, ControlFlowNode value) { + defn.(NameNode).defines(v) and defn.(DefinitionNode).getValue() = value + } + + /** Holds if `v` is defined by assignment of the captured exception. */ + cached predicate exception_capture(Variable v, NameNode defn) { + defn.defines(v) and + exists(ExceptFlowNode ex | ex.getName() = defn) + } + + /** Holds if `v` is defined by a with statement. */ + cached predicate with_definition(Variable v, ControlFlowNode defn) { + exists(With with, Name var | + with.getOptionalVars() = var and + var.getAFlowNode() = defn | + var = v.getAStore() + ) + } + + /** Holds if `v` is defined by multiple assignment at `defn`. */ + cached predicate multi_assignment_definition(Variable v, ControlFlowNode defn) { + defn.(NameNode).defines(v) and + not exists(defn.(DefinitionNode).getValue()) and + exists(SequenceNode s | s.getAnElement() = defn) + } + + /** Holds if `v` is defined by a `for` statement, the definition being `defn` */ + cached predicate iteration_defined_variable(Variable v, ControlFlowNode defn, ControlFlowNode sequence) { + exists(ForNode for | for.iterates(defn, sequence)) and + defn.(NameNode).defines(v) + } + + /** Holds if `v` is a parameter variable and `defn` is the CFG node for that parameter. */ + cached predicate parameter_definition(Variable v, ControlFlowNode defn) { + exists(Function f, Name param | + f.getAnArg() = param or + f.getVararg() = param or + f.getKwarg() = param or + f.getKeywordOnlyArg(_) = param | + defn.getNode() = param and + param.getVariable() = v + ) + } + + /** Holds if `v` is deleted at `del`. */ + cached predicate deletion_definition(Variable v, DeletionNode del) { + del.getTarget().(NameNode).deletes(v) + } + + /** Holds if the name of `var` refers to a submodule of a package and `f` is the entry point + * to the __init__ module of that package. + */ + cached predicate init_module_submodule_defn(Variable var, ControlFlowNode f) { + exists(Module init | + init.isPackageInit() and exists(init.getPackage().getSubModule(var.getId())) and + var instanceof GlobalVariable and init.getEntryNode() = f and + var.getScope() = init + ) + } + + /** Holds if the `v` is in scope at a `from import ... *` and may thus be redefined by that statement */ + cached predicate import_star_refinement(Variable v, ControlFlowNode use, ControlFlowNode def) { + use = def and def instanceof ImportStarNode + and + ( + v.getScope() = def.getScope() + or + exists(NameNode other | + other.uses(v) and + def.getScope() = other.getScope() + ) + ) + } + + /** Holds if an attribute is assigned at `def` and `use` is the use of `v` for that assignment */ + cached predicate attribute_assignment_refinement(Variable v, ControlFlowNode use, ControlFlowNode def) { + use.(NameNode).uses(v) and + def.isStore() and def.(AttrNode).getObject() = use + } + + /** Holds if a `v` is used as an argument to `call`, which *may* modify the object referred to by `v` */ + cached predicate argument_refinement(Variable v, ControlFlowNode use, CallNode call) { + use.(NameNode).uses(v) and + call.getArg(0) = use and + not method_call_refinement(v, _, call) and + not test_refinement(v, _, call) + } + + /** Holds if an attribute is deleted at `def` and `use` is the use of `v` for that deletion */ + cached predicate attribute_deletion_refinement(Variable v, NameNode use, DeletionNode def) { + use.uses(v) and + def.getTarget().(AttrNode).getObject() = use + } + + /** Holds if the set of possible values for `v` is refined by `test` and `use` is the use of `v` in that test. */ + cached predicate test_refinement(Variable v, ControlFlowNode use, ControlFlowNode test) { + use.(NameNode).uses(v) and + test.getAChild*() = use and + test.isBranch() and + exists(BasicBlock block | + block = use.getBasicBlock() and + block = test.getBasicBlock() and + not block.getLastNode() = test + ) + } + +} + +private predicate refinement(Variable v, ControlFlowNode use, ControlFlowNode def) { + SsaSource::import_star_refinement(v, use, def) + or + SsaSource::attribute_assignment_refinement(v, use, def) + or + SsaSource::argument_refinement(v, use, def) + or + SsaSource::attribute_deletion_refinement(v, use, def) + or + SsaSource::test_refinement(v, use, def) + or + SsaSource::method_call_refinement(v, use, def) + or + def = v.(PythonSsaSourceVariable).redefinedAtCallSite() and def = use +} diff --git a/python/ql/src/semmle/python/dataflow/StateTracking.qll b/python/ql/src/semmle/python/dataflow/StateTracking.qll new file mode 100644 index 000000000000..39f240f6388b --- /dev/null +++ b/python/ql/src/semmle/python/dataflow/StateTracking.qll @@ -0,0 +1,174 @@ +/** Provides classes and predicates for tracking global state across the control flow and call graphs. + * + * NOTE: State tracking tracks both whether a state may apply to a given node in a given context *and* + * whether it may not apply. + * That `state.appliesTo(f, ctx)` holds implies nothing about whether `state.mayNotApplyTo(f, ctx)` holds. + * Neither may hold which merely means that `f` with context `ctx` is not reached during the analysis. + * Conversely, both may hold, which means that `state` may or may not apply depending on how `f` was reached. + */ + +import python +private import semmle.python.pointsto.Base +private import semmle.python.pointsto.PointsTo +private import semmle.python.pointsto.PointsToContext + +/** A state that should be tracked. */ +abstract class TrackableState extends string { + + bindingset[this] + TrackableState() { this = this } + + /** Holds if this state may apply to the control flow node `f`, regardless of the context. */ + final predicate appliesTo(ControlFlowNode f) { + this.appliesTo(f, _) + } + + /** Holds if this state may not apply to the control flow node `f`, given the context `ctx`. */ + final predicate appliesTo(ControlFlowNode f, Context ctx) { + StateTracking::appliesToNode(this, f, ctx, true) + } + + /** Holds if this state may apply to the control flow node `f`, given the context `ctx`. */ + final predicate mayNotApplyTo(ControlFlowNode f, Context ctx) { + StateTracking::appliesToNode(this, f, ctx, false) + } + + /** Holds if this state may apply to the control flow node `f`, regardless of the context. */ + final predicate mayNotApplyTo(ControlFlowNode f) { + this.mayNotApplyTo(f, _) + } + + /** Holds if `test` shows value to be untainted with `taint`, given the context `ctx`. */ + predicate testsFor(PyEdgeRefinement test, Context ctx, boolean sense) { + ctx.appliesToScope(test.getScope()) and this.testsFor(test, sense) + } + + /** Holds if `test` shows value to be untainted with `taint` */ + predicate testsFor(PyEdgeRefinement test, boolean sense) { none() } + + /** Holds if state starts at `f`. + * Either this predicate or `startsAt(ControlFlowNode f, Context ctx)` + * should be overriden by sub-classes. + */ + predicate startsAt(ControlFlowNode f) { none() } + + /** Holds if state starts at `f` given context `ctx`. + * Either this predicate or `startsAt(ControlFlowNode f)` + * should be overriden by sub-classes. + */ + pragma [noinline] + predicate startsAt(ControlFlowNode f, Context ctx) { + ctx.appliesTo(f) and this.startsAt(f) + } + + /** Holds if state ends at `f`. + * Either this predicate or `endsAt(ControlFlowNode f, Context ctx)` + * may be overriden by sub-classes. + */ + predicate endsAt(ControlFlowNode f) { none() } + + /** Holds if state ends at `f` given context `ctx`. + * Either this predicate or `endsAt(ControlFlowNode f)` + * may be overriden by sub-classes. + */ + pragma [noinline] + predicate endsAt(ControlFlowNode f, Context ctx) { + ctx.appliesTo(f) and this.endsAt(f) + } + +} + + +module StateTracking { + + private predicate not_allowed(TrackableState state, ControlFlowNode f, Context ctx, boolean sense) { + state.endsAt(f, ctx) and sense = true + or + state.startsAt(f, ctx) and sense = false + } + + /** Holds if `state` may apply (with `sense` = true) or may not apply (with `sense` = false) to + * control flow node `f` given the context `ctx`. + */ + predicate appliesToNode(TrackableState state, ControlFlowNode f, Context ctx, boolean sense) { + state.endsAt(f, ctx) and sense = false + or + state.startsAt(f, ctx) and sense = true + or + not not_allowed(state, f, ctx, sense) + and + ( + exists(BasicBlock b | + /* First node in a block */ + f = b.getNode(0) and appliesAtBlockStart(state, b, ctx, sense) + or + /* Other nodes in block, except trackable calls */ + exists(int n | + f = b.getNode(n) and + appliesToNode(state, b.getNode(n-1), ctx, sense) and + not exists(PyFunctionObject func, Context callee | + callee.fromCall(f, func, ctx) + ) + ) + ) + or + /* Function entry via call */ + exists(FunctionObject func, CallNode call, Context caller | + ctx.fromCall(call, func, caller) and + func.getFunction().getEntryNode() = f and + appliesToNode(state, call.getAPredecessor(), caller, sense) + ) + or + /* Function return */ + exists(PyFunctionObject func, Context callee | + callee.fromCall(f, func, ctx) and + appliesToNode(state, func.getFunction().getANormalExit(), callee, sense) + ) + or + /* Other scope entries */ + exists(Scope s | + s.getEntryNode() = f and + ctx.appliesToScope(s) + | + not exists(Scope pred | pred.precedes(s)) and + (ctx.isImport() or ctx.isRuntime()) and sense = false + or + exists(Scope pred, Context pred_ctx | + appliesToNode(state, pred.getANormalExit(), pred_ctx, sense) and + pred.precedes(s) and + ctx.isRuntime() | + pred_ctx.isRuntime() or pred_ctx.isImport() + ) + ) + ) + } + + /** Holds if `state` may apply (with `sense` = true) or may not apply (with `sense` = false) at the + * start of basic block `block` given the context `ctx`. + */ + private predicate appliesAtBlockStart(TrackableState state, BasicBlock block, Context ctx, boolean sense) { + exists(PyEdgeRefinement test | + test.getSuccessor() = block and + state.testsFor(test, ctx, sense) + ) + or + exists(BasicBlock pred | + pred.getASuccessor() = block and + appliesAtBlockEnd(state, pred, ctx, sense) and + not exists(PyEdgeRefinement test | + test.getPredecessor() = pred and + test.getSuccessor() = block and + state.testsFor(test, sense.booleanNot()) + ) + ) + } + + /** Holds if `state` may apply (with `sense` = true) or may not apply (with `sense` = false) at the + * end of basic block `block` given the context `ctx`. + */ + private predicate appliesAtBlockEnd(TrackableState state, BasicBlock block, Context ctx, boolean sense) { + appliesToNode(state, block.getLastNode(), ctx, sense) + } + +} + diff --git a/python/ql/src/semmle/python/dependencies/Dependencies.qll b/python/ql/src/semmle/python/dependencies/Dependencies.qll new file mode 100644 index 000000000000..f328c43c3148 --- /dev/null +++ b/python/ql/src/semmle/python/dependencies/Dependencies.qll @@ -0,0 +1,199 @@ + +import python +import semmle.python.dependencies.DependencyKind + +private predicate importDependency(Object target, AstNode source) { + source.getScope() != target.getOrigin() and /* Imports of own module are ignored */ + ( + exists(ModuleObject importee, ImportingStmt imp_stmt | + source = imp_stmt and + importee = target | + exists(ImportMember im | imp_stmt.contains(im) | + importee.importedAs(im.getImportedModuleName()) + ) + or + exists(ImportExpr im | imp_stmt.contains(im) | + importee.importedAs(im.getImportedModuleName()) + ) + or + exists(ModuleObject mod | + importDependency(mod, source) and + target = mod.getPackage+() + ) + ) + or + /* from m import name, where m.name is not a submodule */ + exists(PythonModuleObject importee, ImportingStmt imp_stmt | + source = imp_stmt | + exists(ImportMember im | imp_stmt.contains(im) | + importee.importedAs(im.getModule().(ImportExpr).getImportedModuleName()) + and + defn_of_module_attribute(target, importee.getModule(), im.getName()) + ) + ) + ) +} + +class PythonImport extends DependencyKind { + + PythonImport() { + this = "import" + } + + override predicate isADependency(AstNode source, Object target) { + this = this and + importDependency(target, source) + } + +} + +private predicate interesting(Object target) { + target.(ControlFlowNode).getNode() instanceof Scope + or + target instanceof FunctionObject + or + target instanceof ClassObject + or + target instanceof ModuleObject +} + +class PythonUse extends DependencyKind { + + PythonUse() { + this = "use" + } + + override predicate isADependency(AstNode source, Object target) { + interesting(target) and + this = this and + source != target.(ControlFlowNode).getNode() and + exists(ControlFlowNode use, Object obj | + use.getNode() = source and + use.refersTo(obj) and + use.isLoad() + | + interesting(obj) and target = obj + ) + and + not has_more_specific_dependency_source(source) + } + +} + +/** Whether there is a more specific dependency source than this one. + * E.g. if the expression pack.mod.func is a dependency on the function 'func' in 'pack.mod' + * don't make pack.mod depend on the module 'pack.mod' + */ +private predicate has_more_specific_dependency_source(Expr e) { + exists(Attribute member | + member.getObject() = e | + attribute_access_dependency(_, member) + or + has_more_specific_dependency_source(member) + ) +} + +class PythonInheritance extends DependencyKind { + + PythonInheritance() { + this = "inheritance" + } + + override predicate isADependency(AstNode source, Object target) { + this = this and + exists(ClassObject cls | + source = cls.getOrigin() + | + target = cls.getASuperType() + or + target = cls.getAnInferredType() + ) + } + +} + +class PythonAttribute extends DependencyKind { + + PythonAttribute() { + this = "attribute" + } + + override predicate isADependency(AstNode source, Object target) { + this = this and + attribute_access_dependency(target, source) + } + +} + +private predicate attribute_access_dependency(Object target, AstNode source) { + exists(Scope s, string name | + use_of_attribute(source, s, name) and + defn_of_attribute(target, s, name) + ) +} + +private predicate use_of_attribute(Attribute attr, Scope s, string name) { + exists(AttrNode cfg | + cfg.isLoad() and cfg.getNode() = attr + | + exists(Object obj | + cfg.getObject(name).refersTo(obj) | + s = obj.(PythonModuleObject).getModule() or + s = obj.(ClassObject).getPyClass() + ) or + exists(ClassObject cls | + cfg.getObject(name).refersTo(_, cls, _) | + s = cls.getPyClass() + ) + ) + or + exists(SelfAttributeRead sar | + sar = attr | + sar.getClass() = s and + sar.getName() = name + ) +} + +private predicate defn_of_attribute(Object target, Scope s, string name) { + exists(Assign asgn | + target.(ControlFlowNode).getNode() = asgn | + defn_of_instance_attribute(asgn, s, name) + or + defn_of_class_attribute(asgn, s, name) + ) + or + defn_of_module_attribute(target, s, name) +} + +/* Whether asgn defines an instance attribute, that is does + * asgn take the form self.name = ... where self is an instance + * of class c and asgn is not a redefinition. + */ +private predicate defn_of_instance_attribute(Assign asgn, Class c, string name) { + exists(SelfAttributeStore sas | + asgn.getATarget() = sas | + sas.getClass() = c and + sas.getName() = name and + not exists(SelfAttributeStore in_init | + not sas.getScope().(Function).isInitMethod() and + not sas = in_init and + in_init.getClass() = c and + in_init.getName() = name and + in_init.getScope().(Function).isInitMethod() + ) + ) +} + +/* Whether asgn defines an attribute of a class */ +private predicate defn_of_class_attribute(Assign asgn, Class c, string name) { + asgn.getScope() = c and + asgn.getATarget().(Name).getId() = name +} + +/* Holds if `value` is a value assigned to the `name`d attribute of module `m`. */ +private predicate defn_of_module_attribute(ControlFlowNode value, Module m, string name) { + exists(DefinitionNode def | + def.getValue() = value and + def.(NameNode).getId() = name + ) +} diff --git a/python/ql/src/semmle/python/dependencies/DependencyKind.qll b/python/ql/src/semmle/python/dependencies/DependencyKind.qll new file mode 100644 index 000000000000..791723042ac4 --- /dev/null +++ b/python/ql/src/semmle/python/dependencies/DependencyKind.qll @@ -0,0 +1,31 @@ +import semmle.python.dependencies.Dependencies + +/** + * A library describing an abstract mechanism for representing dependency categories. + */ + +/* + * A DependencyCategory is a unique string key used by Architect to identify different categories + * of dependencies that might be viewed independently. + *

    + * The string key defining the category must adhere to the isValid(), otherwise it will not be + * accepted by Architect. + *

    + */ +abstract class DependencyKind extends string { + + bindingset[this] + DependencyKind() { + this = this + } + + /* Tech inventory interface */ + /** + * Identify dependencies associated with this category. + *

    + * The source element is the source of the dependency. + *

    + */ + abstract predicate isADependency(AstNode source, Object target); + +} \ No newline at end of file diff --git a/python/ql/src/semmle/python/dependencies/TechInventory.qll b/python/ql/src/semmle/python/dependencies/TechInventory.qll new file mode 100644 index 000000000000..957ed9fe4815 --- /dev/null +++ b/python/ql/src/semmle/python/dependencies/TechInventory.qll @@ -0,0 +1,116 @@ +import python +import semmle.python.dependencies.Dependencies +import semmle.python.dependencies.DependencyKind + +/** Combine the source-file and package into a single string: + * /path/to/file.py<|>package-name-and-version + */ +string munge(File sourceFile, ExternalPackage package) { + result = "/" + sourceFile.getRelativePath() + "<|>" + package.getName() + "<|>" + package.getVersion() or + not exists(package.getVersion()) and result = "/" + sourceFile.getRelativePath() + "<|>" + package.getName() + "<|>unknown" +} + +abstract class ExternalPackage extends Object { + + ExternalPackage() { + this instanceof ModuleObject + } + + abstract string getName(); + + abstract string getVersion(); + + Object getAttribute(string name) { + result = this.(ModuleObject).getAttribute(name) + } + + PackageObject getPackage() { + result = this.(ModuleObject).getPackage() + } + +} + +bindingset[text] +private predicate is_version(string text) { + text.regexpMatch("\\d+\\.\\d+(\\.\\d+)?([ab]\\d+)?") +} + +bindingset[v] +private string version_format(float v) { + exists(int i, int f | + i = (v+0.05).floor() and f = ((v+0.05-i)*10).floor() | + result = i + "." + f + ) +} + +class DistPackage extends ExternalPackage { + + DistPackage() { + exists(Folder parent | + parent = this.(ModuleObject).getPath().getParent() and + parent.isImportRoot() and + /* Not in standard library */ + not parent.isStdLibRoot(_, _) and + /* Not in the source */ + not exists(parent.getRelativePath()) + ) + } + + /* We don't extract the meta-data for dependencies (yet), so make a best guess from the source + * https://www.python.org/dev/peps/pep-0396/ + */ + private predicate possibleVersion(string version, int priority) { + exists(Object v | + v = this.getAttribute("__version__") and priority = 3 | + version = v.(StringObject).getText() and is_version(version) + or + version = version_format(v.(NumericObject).floatValue()) + or + version = version_format(v.(NumericObject).intValue()) + ) + or + exists(SequenceObject tuple, NumericObject major, NumericObject minor, string base_version | + this.getAttribute("version_info") = tuple and + major = tuple.getInferredElement(0) and minor = tuple.getInferredElement(1) and + base_version = major.intValue() + "." + minor.intValue() | + version = base_version + "." + tuple.getBuiltinElement(2).(NumericObject).intValue() + or + not exists(tuple.getBuiltinElement(2)) and version = base_version + ) and priority = 2 + or + exists(string v | + v.toLowerCase() = "version" | + is_version(version) and + version = this.getAttribute(v).(StringObject).getText() + ) and priority = 1 + } + + override string getVersion() { + this.possibleVersion(result, max(int priority | this.possibleVersion(_, priority))) + } + + override string getName() { + result = this.(ModuleObject).getShortName() + } + + predicate fromSource(Object src) { + exists(ModuleObject m | + m.getModule() = src.(ControlFlowNode).getEnclosingModule() or + src = m | + m = this or + m.getPackage+() = this and + not exists(DistPackage inter | + m.getPackage*() = inter and + inter.getPackage+() = this + ) + ) + } + +} + +predicate dependency(AstNode src, DistPackage package) { + exists(DependencyKind cat, Object target | + cat.isADependency(src, target) | + package.fromSource(target) + ) +} diff --git a/python/ql/src/semmle/python/filters/GeneratedCode.qll b/python/ql/src/semmle/python/filters/GeneratedCode.qll new file mode 100644 index 000000000000..8b6f780ce555 --- /dev/null +++ b/python/ql/src/semmle/python/filters/GeneratedCode.qll @@ -0,0 +1,185 @@ +import python +import semmle.python.templates.Templates + +/** + * A file that is detected as being generated. + */ +abstract class GeneratedFile extends File { + + abstract string getTool(); + +} + +/* We distinguish between a "lax" match which just includes "generated by" or similar versus a "strict" match which includes "this file is generated by" or similar + * "lax" matches are taken to indicate generated file if they occur at the top of a file. "strict" matches can occur anywhere. + * There is no formal reason for the above, it just seems to work well in practice. + */ + +library class GenericGeneratedFile extends GeneratedFile { + + GenericGeneratedFile() { + not this instanceof SpecificGeneratedFile + and + ( + (lax_generated_by(this, _) or lax_generated_from(this, _)) and dont_modify(this) + or + strict_generated_by(this, _) or strict_generated_from(this, _) + ) + } + + override string getTool() { + lax_generated_by(this, result) or strict_generated_by(this, result) + } + +} + +private string comment_or_docstring(File f, boolean before_code) { + exists(Comment c | + c.getLocation().getFile() = f and + result = c.getText() | + if exists(Stmt s | s.getEnclosingModule().getFile() = f and s.getLocation().getStartLine() < c.getLocation().getStartLine()) then + before_code = false + else + before_code = true + ) + or + exists(Module m | m.getFile() = f | + result = m.getDocString().getText() and + before_code = true + ) + +} + +private predicate lax_generated_by(File f, string tool) { + exists(string comment | comment = comment_or_docstring(f, _) | + tool = comment.regexpCapture("(?is).*\\b(?:(?:auto[ -]?)?generated|created automatically) by (?:the )?([-/\\w.]+[-/\\w]).*", 1) + ) +} + +private predicate lax_generated_from(File f, string src) { + exists(string comment | comment = comment_or_docstring(f, _) | + src = comment.regexpCapture("(?is).*\\b((?:auto[ -]?)?generated|created automatically) from ([-/\\w.]+[-/\\w]).*", 1) + ) +} + +private predicate strict_generated_by(File f, string tool) { + exists(string comment | comment = comment_or_docstring(f, true) | + tool = comment.regexpCapture("(?is)# *(?:this +)?(?:(?:code|file) +)?(?:is +)?(?:(?:auto(?:matically)?[ -]?)?generated|created automatically) by (?:the )?([-/\\w.]+[-/\\w]).*", 1) + ) +} + +private predicate strict_generated_from(File f, string src) { + exists(string comment | comment = comment_or_docstring(f, true) | + src = comment.regexpCapture("(?is)# *(?:this +)?(?:(?:code|file) +)?(?:is +)?(?:(?:auto(?:matically)?[ -]?)?generated|created automatically) from ([-/\\w.]+[-/\\w]).*", 1) + ) +} + +private predicate dont_modify(File f) { + comment_or_docstring(f, _).regexpMatch("(?is).*\\b(Do not|Don't) (edit|modify|make changes)\\b.*") +} + + +/** + * A file generated by a template engine + */ +abstract library class SpecificGeneratedFile extends GeneratedFile { + /* Currently cover Spitfire, Pyxl and Mako. + * Django templates are not compiled to Python. + * Jinja2 templates are compiled direct to bytecode via the ast. + */ +} + +/** File generated by the spitfire templating engine */ +class SpitfireGeneratedFile extends SpecificGeneratedFile { + + SpitfireGeneratedFile() { + exists(Module m | + m.getFile() = this and not m instanceof SpitfireTemplate | + exists(ImportMember template_method, ImportExpr spitfire_runtime_template | + spitfire_runtime_template.getName() = "spitfire.runtime.template" and + template_method.getModule() = spitfire_runtime_template and + template_method.getName() = "template_method" + ) + ) + } + + override string getTool() { + result = "spitfire" + } + +} + +/** File generated by the pyxl templating engine */ +class PyxlGeneratedFile extends SpecificGeneratedFile { + + PyxlGeneratedFile() { + this.getSpecifiedEncoding() = "pyxl" + } + + override string getTool() { + result = "pyxl" + } + +} + +/** File generated by the mako templating engine */ +class MakoGeneratedFile extends SpecificGeneratedFile { + + MakoGeneratedFile() { + exists(Module m | m.getFile() = this | + from_mako_import(m) = "runtime" and + from_mako_import(m) = "filters" and + from_mako_import(m) = "cache" and + exists(Assign a, Name n | + a.getScope() = m and a.getATarget() = n and n.getId() = "__M_dict_builtin" + ) and + exists(Assign a, Name n | + a.getScope() = m and a.getATarget() = n and n.getId() = "__M_locals_builtin" + ) and + exists(Assign a, Name n | + a.getScope() = m and a.getATarget() = n and n.getId() = "_magic_number" + ) + ) + } + + override string getTool() { + result = "mako" + } + +} + +string from_mako_import(Module m) { + exists(ImportMember member, ImportExpr mako | + member.getScope() = m and + member.getModule() = mako and + mako.getName() = "mako" | + result = member.getName() + ) +} + +/** File generated by Google's protobuf tool. */ +class ProtobufGeneratedFile extends SpecificGeneratedFile { + + ProtobufGeneratedFile() { + this.getName().regexpMatch(".*_pb2?.py") + and + exists(Module m | + m.getFile() = this | + exists(ImportExpr imp | + imp.getEnclosingModule() = m | + imp.getImportedModuleName() = "google.net.proto2.python.public" + ) + and + exists(AssignStmt a, Name n | + a.getEnclosingModule() = m and + a.getATarget() = n and + n.getId() = "DESCRIPTOR" + ) + ) + } + + override string getTool() { + result = "protobuf" + } + +} diff --git a/python/ql/src/semmle/python/filters/Tests.qll b/python/ql/src/semmle/python/filters/Tests.qll new file mode 100644 index 000000000000..e9cfedc499ff --- /dev/null +++ b/python/ql/src/semmle/python/filters/Tests.qll @@ -0,0 +1,48 @@ +import python + +abstract class TestScope extends Scope {} + +// don't extend Class directly to avoid ambiguous method warnings +class UnitTestClass extends TestScope { + UnitTestClass() { + exists(ClassObject c | + this = c.getPyClass() | + c.getASuperType() = theUnitTestPackage().getAttribute(_) + or + c.getASuperType().getName().toLowerCase() = "testcase" + ) + } +} + +PackageObject theUnitTestPackage() { + result.getName() = "unittest" +} + +abstract class Test extends TestScope {} + +class UnitTestFunction extends Test { + + UnitTestFunction() { + this.getScope+() instanceof UnitTestClass + and + this.(Function).getName().matches("test%") + } +} + +class PyTestFunction extends Test { + + PyTestFunction() { + exists(Module pytest | pytest.getName() = "pytest") and + this.(Function).getName().matches("test%") + } + +} + +class NoseTestFunction extends Test { + + NoseTestFunction() { + exists(Module nose | nose.getName() = "nose") and + this.(Function).getName().matches("test%") + } + +} diff --git a/python/ql/src/semmle/python/flow/NameNode.qll b/python/ql/src/semmle/python/flow/NameNode.qll new file mode 100644 index 000000000000..0dab82fe8656 --- /dev/null +++ b/python/ql/src/semmle/python/flow/NameNode.qll @@ -0,0 +1,143 @@ +import python +private import semmle.python.pointsto.Base + +/** A control flow node corresponding to a (plain variable) name expression, such as `var`. + * `None`, `True` and `False` are excluded. + */ +class NameNode extends ControlFlowNode { + + NameNode() { + exists(Name n | py_flow_bb_node(this, n, _, _)) + or + exists(PlaceHolder p | py_flow_bb_node(this, p, _, _)) + } + + /** Whether this flow node defines the variable `v`. */ + 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(Variable v) { + exists(Name d | this.getNode() = d and d.deletes(v)) + } + + /** Whether this flow node uses the variable `v`. */ + predicate uses(Variable v) { + this.isLoad() and exists(Name u | this.getNode() = u and u.uses(v)) + or + exists(PlaceHolder u | this.getNode() = u and u.getVariable() = v and u.getCtx() instanceof Load) + or + use_of_global_variable(this, v.getScope(), v.getId()) + } + + string getId() { + result = this.getNode().(Name).getId() + or + result = this.getNode().(PlaceHolder).getId() + } + + /** Whether this is a use of a local variable. */ + predicate isLocal() { + local(this) + } + + /** Whether this is a use of a non-local variable. */ + predicate isNonLocal() { + non_local(this) + } + + /** Whether this is a use of a global (including builtin) variable. */ + predicate isGlobal() { + use_of_global_variable(this, _, _) + } + + predicate isSelf() { + exists(SsaVariable selfvar | + selfvar.isSelf() and selfvar.getAUse() = this + ) + } + +} + +private predicate fast_local(NameNode n) { + exists(FastLocalVariable v | + n.uses(v) and + v.getScope() = n.getScope() + ) +} + +private predicate local(NameNode n) { + fast_local(n) + or + exists(SsaVariable var | + var.getAUse() = n and + n.getScope() instanceof Class and + exists(var.getDefinition()) + ) +} + +private predicate non_local(NameNode n) { + exists(FastLocalVariable flv | + flv.getALoad() = n.getNode() and + not flv.getScope() = n.getScope() + ) +} + +// magic is fine, but we get questionable join-ordering of it +pragma [nomagic] +private predicate use_of_global_variable(NameNode n, Module scope, string name) { + n.isLoad() and + not non_local(n) + and + not exists(SsaVariable var | + var.getAUse() = n | + var.getVariable() instanceof FastLocalVariable + or + n.getScope() instanceof Class and + not maybe_undefined(var) + ) + and name = n.getId() + and scope = n.getEnclosingModule() +} + +private predicate maybe_defined(SsaVariable var) { + exists(var.getDefinition()) and not py_ssa_phi(var, _) and not var.getDefinition().isDelete() + or + exists(SsaVariable input | + input = var.getAPhiInput() | + maybe_defined(input) + ) +} + +private predicate maybe_undefined(SsaVariable var) { + not exists(var.getDefinition()) and not py_ssa_phi(var, _) + or + var.getDefinition().isDelete() + or + maybe_undefined(var.getAPhiInput()) + or + exists(BasicBlock incoming | + exists(var.getAPhiInput()) and + incoming.getASuccessor() = var.getDefinition().getBasicBlock() and + not var.getAPhiInput().getDefinition().getBasicBlock().dominates(incoming) + ) +} + +/** A control flow node corresponding to a named constant, one of `None`, `True` or `False`. */ +class NameConstantNode extends NameNode { + + NameConstantNode() { + exists(NameConstant n | py_flow_bb_node(this, n, _, _)) + } + + override deprecated predicate defines(Variable v) { none() } + + override deprecated predicate deletes(Variable v) { none() } + + /* We ought to override uses as well, but that has + * a serious performance impact. + deprecated predicate uses(Variable v) { none() } + */ +} diff --git a/python/ql/src/semmle/python/libraries/Zope.qll b/python/ql/src/semmle/python/libraries/Zope.qll new file mode 100644 index 000000000000..f355982dfc76 --- /dev/null +++ b/python/ql/src/semmle/python/libraries/Zope.qll @@ -0,0 +1,29 @@ +/** Utilities for handling the zope libraries */ + +import python + +/** A method that to a sub-class of `zope.interface.Interface` */ +class ZopeInterfaceMethod extends PyFunctionObject { + + /** Holds if this method belongs to a class that sub-classes `zope.interface.Interface` */ + ZopeInterfaceMethod() { + exists(ModuleObject zope, Object interface, ClassObject owner | + zope.getAttribute("Interface") = interface and + zope.getName() = "zope.interface" and + owner.declaredAttribute(_) = this and + owner.getAnImproperSuperType().getABaseType() = interface + ) + } + + override int minParameters() { + result = super.minParameters() + 1 + } + + override int maxParameters() { + if exists(this.getFunction().getVararg()) then + result = super.maxParameters() + else + result = super.maxParameters() + 1 + } + +} diff --git a/python/ql/src/semmle/python/pointsto/Base.qll b/python/ql/src/semmle/python/pointsto/Base.qll new file mode 100644 index 000000000000..cc57a6f5d69d --- /dev/null +++ b/python/ql/src/semmle/python/pointsto/Base.qll @@ -0,0 +1,654 @@ +/** + * Combined points-to and type-inference for "run-time" (as opposed to "import-time" values) + * The main relation `runtime_points_to(node, object, cls, origin)` relates a control flow node + * to the possible objects it points-to the inferred types of those objects and the 'origin' + * of those objects. The 'origin' is the point in source code that the object can be traced + * back to. + * + * This file contains non-layered parts of the points-to analysis. + */ +import python +import semmle.python.dataflow.SsaDefinitions + +module BasePointsTo { + /** INTERNAL -- Use n.refersTo(value, _, origin) instead */ + pragma [noinline] + predicate points_to(ControlFlowNode f, Object value, ControlFlowNode origin) { + ( + f.isLiteral() and value = f and not f.getNode() instanceof ImmutableLiteral + or + f.isFunction() and value = f + ) and origin = f + } +} + +/** The kwargs parameter (**kwargs) in a function definition is always a dict */ +predicate kwargs_points_to(ControlFlowNode f, ClassObject cls) { + exists(Function func | func.getKwarg() = f.getNode()) and + cls = theDictType() +} + +/** The varargs (*varargs) in a function definition is always a tuple */ +predicate varargs_points_to(ControlFlowNode f, ClassObject cls) { + exists(Function func | func.getVararg() = f.getNode()) and + cls = theTupleType() +} + +/** Gets the class of the object for simple cases, namely constants, functions, + * comprehensions and built-in objects. + * + * This exists primarily for internal use. Use getAnInferredType() instead. + */ +pragma [noinline] +ClassObject simple_types(Object obj) { + result = comprehension(obj.getOrigin()) + or + result = collection_literal(obj.getOrigin()) + or + obj.getOrigin() instanceof CallableExpr and result = thePyFunctionType() + or + obj.getOrigin() instanceof Module and result = theModuleType() + or + result = builtin_object_type(obj) +} + +private ClassObject comprehension(Expr e) { + e instanceof ListComp and result = theListType() + or + e instanceof SetComp and result = theSetType() + or + e instanceof DictComp and result = theDictType() + or + e instanceof GeneratorExp and result = theGeneratorType() +} + +private ClassObject collection_literal(Expr e) { + e instanceof List and result = theListType() + or + e instanceof Set and result = theSetType() + or + e instanceof Dict and result = theDictType() + or + e instanceof Tuple and result = theTupleType() +} + +private int tuple_index_value(Object t, int i) { + result = t.(TupleNode).getElement(i).getNode().(Num).getN().toInt() + or + exists(Object item | + py_citems(t, i, item) and + result = item.(NumericObject).intValue() + ) +} + +pragma [noinline] +int version_tuple_value(Object t) { + not exists(tuple_index_value(t, 1)) and result = tuple_index_value(t, 0)*10 + or + not exists(tuple_index_value(t, 2)) and result = tuple_index_value(t, 0)*10 + tuple_index_value(t, 1) + or + tuple_index_value(t, 2) = 0 and result = tuple_index_value(t, 0)*10 + tuple_index_value(t, 1) + or + tuple_index_value(t, 2) > 0 and result = tuple_index_value(t, 0)*10 + tuple_index_value(t, 1) + 1 +} + +/** Choose a version numbers that represent the extreme of supported versions. */ +private int major_minor() { + if major_version() = 3 then + (result = 33 or result = 37) // 3.3 to 3.7 + else + (result = 25 or result = 27) // 2.5 to 2.7 +} + +/** Compares the given tuple object to both the maximum and minimum possible sys.version_info values */ +int version_tuple_compare(Object t) { + version_tuple_value(t) < major_minor() and result = -1 + or + version_tuple_value(t) = major_minor() and result = 0 + or + version_tuple_value(t) > major_minor() and result = 1 +} + +/* Holds if `cls` is a new-style class if it were to have no explicit base classes */ +predicate baseless_is_new_style(ClassObject cls) { + cls.isBuiltin() + or + major_version() = 3 + or + exists(cls.declaredMetaClass()) +} + +/* The following predicates exist in order to provide + * more precise type information than the underlying + * database relations. This help to optimise the points-to + * analysis. + */ + +/** Gets the base class of built-in class `cls` */ +pragma [noinline] +ClassObject builtin_base_type(ClassObject cls) { + /* The extractor uses the special name ".super." to indicate the super class of a builtin class */ + py_cmembers_versioned(cls, ".super.", result, _) +} + +/** Gets the `name`d attribute of built-in class `cls` */ +pragma [noinline] +Object builtin_class_attribute(ClassObject cls, string name) { + not name = ".super." and + py_cmembers_versioned(cls, name, result, _) +} + +/** Holds if the `name`d attribute of built-in module `m` is `value` of `cls` */ +pragma [noinline] +predicate builtin_module_attribute(ModuleObject m, string name, Object value, ClassObject cls) { + py_cmembers_versioned(m, name, value, _) and cls = builtin_object_type(value) +} + +/** Gets the (built-in) class of the built-in object `obj` */ +pragma [noinline] +ClassObject builtin_object_type(Object obj) { + py_cobjecttypes(obj, result) and not obj = unknownValue() + or + obj = unknownValue() and result = theUnknownType() +} + +/** Holds if this class (not on a super-class) declares name */ +pragma [noinline] +predicate class_declares_attribute(ClassObject cls, string name) { + exists(Class defn | + defn = cls.getPyClass() and + class_defines_name(defn, name) + ) + or + exists(Object o | + o = builtin_class_attribute(cls, name) and + not exists(ClassObject sup | + sup = builtin_base_type(cls) and + o = builtin_class_attribute(sup, name) + ) + ) +} + +/** Holds if the class defines name */ +private predicate class_defines_name(Class cls, string name) { + exists(SsaVariable var | name = var.getId() and var.getAUse() = cls.getANormalExit()) +} + +/** Gets a return value CFG node, provided that is safe to track across returns */ +ControlFlowNode safe_return_node(PyFunctionObject func) { + result = func.getAReturnedNode() + // Not a parameter + and not exists(Parameter p, SsaVariable pvar | + p.asName().getAFlowNode() = pvar.getDefinition() and + result = pvar.getAUse() + ) and + // No alternatives + not exists(ControlFlowNode branch | branch.isBranch() and branch.getScope() = func.getFunction()) +} + +/** Holds if it can be determined from the control flow graph alone that this function can never return */ +predicate function_can_never_return(FunctionObject func) { + /* A Python function never returns if it has no normal exits that are not dominated by a + * call to a function which itself never returns. + */ + exists(Function f | + f = func.getFunction() and + not exists(f.getAnExitNode()) + ) + or + func = theExitFunctionObject() +} + +/** Python specific sub-class of generic EssaNodeDefinition */ +class PyNodeDefinition extends EssaNodeDefinition { + + PyNodeDefinition() { + this.getSourceVariable().hasDefiningNode(this.getDefiningNode()) + } + + override string getRepresentation() { + result = this.getAQlClass() + } + +} + +/** Python specific sub-class of generic EssaNodeRefinement */ +class PyNodeRefinement extends EssaNodeRefinement { + + override string getRepresentation() { + result = this.getAQlClass() + "(" + this.getInput().getRepresentation() + ")" + or + not exists(this.getInput()) and + result = this.getAQlClass() + "(" + this.getSourceVariable().getName() + "??)" + } +} + +/** An assignment to a variable `v = val` */ +class AssignmentDefinition extends PyNodeDefinition { + + AssignmentDefinition() { + SsaSource::assignment_definition(this.getSourceVariable(), this.getDefiningNode(), _) + } + + ControlFlowNode getValue() { + SsaSource::assignment_definition(this.getSourceVariable(), this.getDefiningNode(), result) + } + + override string getRepresentation() { + result = this.getValue().getNode().toString() + } + +} + +/** Capture of a raised exception `except ExceptionType ex:` */ +class ExceptionCapture extends PyNodeDefinition { + + ExceptionCapture() { + SsaSource::exception_capture(this.getSourceVariable(), this.getDefiningNode()) + } + + ControlFlowNode getType() { + exists(ExceptFlowNode ex | + ex.getName() = this.getDefiningNode() and + result = ex.getType() + ) + } + + override string getRepresentation() { + result = "except " + this.getSourceVariable().getName() + } + +} +/** An assignment to a variable as part of a multiple assignment `..., v, ... = val` */ +class MultiAssignmentDefinition extends PyNodeDefinition { + + MultiAssignmentDefinition() { + SsaSource::multi_assignment_definition(this.getSourceVariable(), this.getDefiningNode()) + } + + override string getRepresentation() { + result = "..." + } + +} + + +class WithDefinition extends PyNodeDefinition { + + WithDefinition () { + SsaSource::with_definition(this.getSourceVariable(), this.getDefiningNode()) + } + + override string getRepresentation() { + result = "with" + } + +} + +/** A definition of a variable by declaring it as a parameter */ +class ParameterDefinition extends PyNodeDefinition { + + ParameterDefinition() { + SsaSource::parameter_definition(this.getSourceVariable(), this.getDefiningNode()) + } + + predicate isSelf() { + this.getDefiningNode().getNode().(Parameter).isSelf() + } + + ControlFlowNode getDefault() { + result.getNode() = this.getParameter().getDefault() + } + + Parameter getParameter() { + result = this.getDefiningNode().getNode() + } + +} + +/** A definition of a variable in a for loop `for v in ...:` */ +class IterationDefinition extends PyNodeDefinition { + + ControlFlowNode sequence; + + IterationDefinition() { + SsaSource::iteration_defined_variable(this.getSourceVariable(), this.getDefiningNode(), sequence) + } + + ControlFlowNode getSequence() { + result = sequence + } + +} + +/** A deletion of a variable `del v` */ +class DeletionDefinition extends PyNodeDefinition { + + DeletionDefinition() { + SsaSource::deletion_definition(this.getSourceVariable(), this.getDefiningNode()) + } + +} + +/** Definition of variable at the entry of a scope. Usually this represents the transfer of + * a global or non-local variable from one scope to another. + */ +class ScopeEntryDefinition extends PyNodeDefinition { + + ScopeEntryDefinition() { + this.getDefiningNode() = this.getSourceVariable().(PythonSsaSourceVariable).getScopeEntryDefinition() and + not this instanceof ImplicitSubModuleDefinition + } + + override Scope getScope() { + result.getEntryNode() = this.getDefiningNode() + } + +} + +/** Possible redefinition of variable via `from ... import *` */ +class ImportStarRefinement extends PyNodeRefinement { + + ImportStarRefinement() { + SsaSource::import_star_refinement(this.getSourceVariable(), _, this.getDefiningNode()) + } + +} + +/** Assignment of an attribute `obj.attr = val` */ +class AttributeAssignment extends PyNodeRefinement { + + AttributeAssignment() { + SsaSource::attribute_assignment_refinement(this.getSourceVariable(), _, this.getDefiningNode()) + } + + string getName() { + result = this.getDefiningNode().(AttrNode).getName() + } + + ControlFlowNode getValue() { + result = this.getDefiningNode().(DefinitionNode).getValue() + } + + override string getRepresentation() { + result = this.getAQlClass() + " '" + this.getName() + "'(" + this.getInput().getRepresentation() + ")" + or + not exists(this.getInput()) and + result = this.getAQlClass() + " '" + this.getName() + "'(" + this.getSourceVariable().getName() + "??)" + } + +} + +/** A use of a variable as an argument, `foo(v)`, which might modify the object referred to. */ +class ArgumentRefinement extends PyNodeRefinement { + + ControlFlowNode argument; + + ArgumentRefinement() { + SsaSource::argument_refinement(this.getSourceVariable(), argument, this.getDefiningNode()) + } + + ControlFlowNode getArgument() { result = argument } + +} + +/** Deletion of an attribute `del obj.attr`. */ +class EssaAttributeDeletion extends PyNodeRefinement { + + EssaAttributeDeletion() { + SsaSource::attribute_deletion_refinement(this.getSourceVariable(), _, this.getDefiningNode()) + } + + string getName() { + result = this.getDefiningNode().(AttrNode).getName() + } + +} + +/** A pi-node (guard) with only one successor. */ +class SingleSuccessorGuard extends PyNodeRefinement { + + SingleSuccessorGuard() { + SsaSource::test_refinement(this.getSourceVariable(), _, this.getDefiningNode()) + } + + boolean getSense() { + exists(this.getDefiningNode().getAFalseSuccessor()) and result = false + or + exists(this.getDefiningNode().getATrueSuccessor()) and result = true + } + + override string getRepresentation() { + result = PyNodeRefinement.super.getRepresentation() + " [" + this.getSense().toString() + "]" + or + not exists(this.getSense()) and + result = PyNodeRefinement.super.getRepresentation() + " [??]" + } +} + +/** Implicit definition of the names of sub-modules in a package. + * Although the interpreter does not pre-define these names, merely populating them + * as they are imported, this is a good approximation for static analysis. + */ +class ImplicitSubModuleDefinition extends PyNodeDefinition { + + ImplicitSubModuleDefinition() { + SsaSource::init_module_submodule_defn(this.getSourceVariable(), this.getDefiningNode()) + } + +} + +/** An implicit (possible) definition of an escaping variable at a call-site */ +class CallsiteRefinement extends PyNodeRefinement { + + override string toString() { + result = "CallsiteRefinement" + } + + CallsiteRefinement() { + exists(PythonSsaSourceVariable var, ControlFlowNode defn | + defn = var.redefinedAtCallSite() and + this.definedBy(var, defn) and + not this instanceof ArgumentRefinement and + not this instanceof MethodCallsiteRefinement and + not this instanceof SingleSuccessorGuard + ) + } + + CallNode getCall() { + this.getDefiningNode() = result + } + +} + +/** An implicit (possible) modification of the object referred at a method call */ +class MethodCallsiteRefinement extends PyNodeRefinement { + + MethodCallsiteRefinement() { + SsaSource::method_call_refinement(this.getSourceVariable(), _, this.getDefiningNode()) + and not this instanceof SingleSuccessorGuard + } + + CallNode getCall() { + this.getDefiningNode() = result + } + +} + +/** An implicit (possible) modification of `self` at a method call */ +class SelfCallsiteRefinement extends MethodCallsiteRefinement { + + SelfCallsiteRefinement() { + this.getSourceVariable().(Variable).isSelf() + } + +} + +/** Python specific sub-class of generic EssaEdgeRefinement */ +class PyEdgeRefinement extends EssaEdgeRefinement { + + override string getRepresentation() { + /* This is for testing so use capital 'P' to make it sort before 'phi' and + * be more visually distinctive. */ + result = "Pi(" + this.getInput().getRepresentation() + ") [" + this.getSense() + "]" + or + not exists(this.getInput()) and + result = "Pi(" + this.getSourceVariable().getName() + "??) [" + this.getSense() + "]" + } + + ControlFlowNode getTest() { + result = this.getPredecessor().getLastNode() + } + +} + +/** Hold if outer contains inner, both are contained within a test and inner is a use is a plain use or an attribute lookup */ +pragma[noinline] +predicate contains_interesting_expression_within_test(ControlFlowNode outer, ControlFlowNode inner) { + inner.isLoad() and + exists(ControlFlowNode test | + outer.getAChild*() = inner and + test_contains(test, outer) and + test_contains(test, inner) | + inner instanceof NameNode or + inner instanceof AttrNode + ) +} + +/** Hold if `expr` is a test (a branch) and `use` is within that test */ +predicate test_contains(ControlFlowNode expr, ControlFlowNode use) { + expr.getNode() instanceof Expr and + expr.isBranch() and + expr.getAChild*() = use +} + +/** Holds if `test` is a test (a branch), `use` is within that test and `def` is an edge from that test with `sense` */ +predicate refinement_test(ControlFlowNode test, ControlFlowNode use, boolean sense, PyEdgeRefinement def) { + /* Because calls such as `len` may create a new variable, we need to go via the source variable + * That is perfectly safe as we are only dealing with calls that do not mutate their arguments. + */ + use = def.getInput().getSourceVariable().(Variable).getAUse() and + test = def.getPredecessor().getLastNode() and + test_contains(test, use) and + sense = def.getSense() +} + +/** Holds if `f` is an import of the form `from .[...] import name` and the enclosing scope is an __init__ module */ +pragma [noinline] +predicate live_import_from_dot_in_init(ImportMemberNode f, EssaVariable var) { + exists(string name | + import_from_dot_in_init(f.getModule(name)) and + var.getSourceVariable().getName() = name and var.getAUse() = f + ) +} + +/** Holds if `f` is an import of the form `from .[...] import ...` and the enclosing scope is an __init__ module */ +predicate import_from_dot_in_init(ImportExprNode f) { + f.getScope() = any(Module m).getInitModule() and + ( + f.getNode().getLevel() = 1 and + not exists(f.getNode().getName()) + or + f.getNode().getImportedModuleName() = f.getEnclosingModule().getPackage().getName() + ) +} + +/** Gets the pseudo-object representing the value referred to by an undefined variable */ +Object undefinedVariable() { + py_special_objects(result, "_semmle_undefined_value") +} + +/** Gets the pseudo-object representing an unknown value */ +Object unknownValue() { + py_special_objects(result, "_1") +} + +BuiltinCallable theTypeNewMethod() { + py_cmembers_versioned(theTypeType(), "__new__", result, major_version().toString()) +} + +/** Gets the `value, cls, origin` that `f` would refer to if it has not been assigned some other value */ +pragma [noinline] +predicate potential_builtin_points_to(NameNode f, Object value, ClassObject cls, ControlFlowNode origin) { + f.isGlobal() and f.isLoad() and origin = f and + ( + builtin_name_points_to(f.getId(), value, cls) + or + not exists(builtin_object(f.getId())) and value = unknownValue() and cls = theUnknownType() + ) +} + +pragma [noinline] +predicate builtin_name_points_to(string name, Object value, ClassObject cls) { + value = builtin_object(name) and py_cobjecttypes(value, cls) +} + +module BaseFlow { + + predicate reaches_exit(EssaVariable var) { + var.getAUse() = var.getScope().getANormalExit() + } + + /* Helper for this_scope_entry_value_transfer(...). Transfer of values from earlier scope to later on */ + pragma [noinline] + predicate scope_entry_value_transfer_from_earlier(EssaVariable pred_var, Scope pred_scope, ScopeEntryDefinition succ_def, Scope succ_scope) { + exists(SsaSourceVariable var | + reaches_exit(pred_var) and + pred_var.getScope() = pred_scope and + var = pred_var.getSourceVariable() and + var = succ_def.getSourceVariable() and + succ_def.getScope() = succ_scope + | + pred_scope.precedes(succ_scope) + or + /* If an `__init__` method does not modify the global variable, then + * we can skip it and take the value directly from the module. + */ + exists(Scope init | + init.getName() = "__init__" and init.precedes(succ_scope) and pred_scope.precedes(init) and + not var.(Variable).getAStore().getScope() = init and var instanceof GlobalVariable + ) + ) + } +} + +/** Points-to for syntactic elements where context is not relevant */ +predicate simple_points_to(ControlFlowNode f, Object value, ClassObject cls, ControlFlowNode origin) { + kwargs_points_to(f, cls) and value = f and origin = f + or + varargs_points_to(f, cls) and value = f and origin = f + or + BasePointsTo::points_to(f, value, origin) and cls = simple_types(value) + or + value = f.getNode().(ImmutableLiteral).getLiteralObject() and cls = simple_types(value) and origin = f +} + +/** Holds if `bit` is a binary expression node with a bitwise operator. + * Helper for `this_binary_expr_points_to`. + */ +predicate bitwise_expression_node(BinaryExprNode bit, ControlFlowNode left, ControlFlowNode right) { + exists(Operator op | + op = bit.getNode().getOp() | + op instanceof BitAnd or + op instanceof BitOr or + op instanceof BitXor + ) and + left = bit.getLeft() and + right = bit.getRight() +} + + +private +Module theCollectionsAbcModule() { + result.getName() = "_abcoll" + or + result.getName() = "_collections_abc" +} + +ClassObject collectionsAbcClass(string name) { + exists(Class cls | + result.getPyClass() = cls and + cls.getName() = name and + cls.getScope() = theCollectionsAbcModule() + ) +} diff --git a/python/ql/src/semmle/python/pointsto/CallGraph.qll b/python/ql/src/semmle/python/pointsto/CallGraph.qll new file mode 100644 index 000000000000..cc618690c324 --- /dev/null +++ b/python/ql/src/semmle/python/pointsto/CallGraph.qll @@ -0,0 +1,73 @@ +/** + * Context-sensitive call-graph. + * + * NOTE: Since an "invocation" contains callsite information + * and a path back to its ancestor calls, the "invocation" call-graph must be a tree. + * This has two important consequences: + * 1. The graph is incomplete; it has quite limited depth in order to keep the graph to a sensible size. + * 2. The graph is precise. Since different invocations are distinct, there can be no "cross-talk" between + * different calls to the same function. + */ +import python +private import semmle.python.pointsto.PointsToContext + +private newtype TTInvocation = TInvocation(FunctionObject f, Context c) { + exists(Context outer, CallNode call | + call = f.getACall(outer) and + c.fromCall(call, outer) + ) + or + c.appliesToScope(f.getFunction()) +} + +/** This class represents a static approximation to the + * dynamic call-graph. A `FunctionInvocation` represents + * all calls made to a function for a given context. + */ +class FunctionInvocation extends TTInvocation { + + string toString() { result = "Invocation" } + + FunctionObject getFunction() { this = TInvocation(result, _) } + + Context getContext() { this = TInvocation(_, result) } + + /** Gets the callee invocation for the given callsite. + * The callsite must be within the function of this invocation. + */ + FunctionInvocation getCallee(CallNode call) { + exists(FunctionObject callee, Context callee_context, FunctionObject caller, Context caller_context | + this = TInvocation(caller, caller_context) and + result = TInvocation(callee, callee_context) and + call = callee.getACall(caller_context) and + callee_context.fromCall(call, caller_context) and + call.getScope() = caller.getFunction() + ) + } + + /** Gets a callee invocation. + * That is any invocation made from within this invocation. + */ + FunctionInvocation getACallee() { + result = this.getCallee(_) + } + + /** Holds if this is an invocation `f` in the "runtime" context. */ + predicate runtime(FunctionObject f) { + exists(Context c | + c.isRuntime() and + this = TInvocation(f, c) + ) + } + + /** Gets the call from which this invocation was made. */ + CallNode getCall() { + this.getContext().fromCall(result, _) + } + + /** Gets the caller invocation of this invocation, if any. */ + FunctionInvocation getCaller() { + this = result.getCallee(_) + } + +} diff --git a/python/ql/src/semmle/python/pointsto/Context.qll b/python/ql/src/semmle/python/pointsto/Context.qll new file mode 100644 index 000000000000..156e8eb43b4a --- /dev/null +++ b/python/ql/src/semmle/python/pointsto/Context.qll @@ -0,0 +1,4 @@ +import python +private import semmle.python.pointsto.PointsToContext + +class Context = PointsToContext; \ No newline at end of file diff --git a/python/ql/src/semmle/python/pointsto/Filters.qll b/python/ql/src/semmle/python/pointsto/Filters.qll new file mode 100644 index 000000000000..6ac515b217a4 --- /dev/null +++ b/python/ql/src/semmle/python/pointsto/Filters.qll @@ -0,0 +1,47 @@ +/** Helper predicates for standard tests in Python commonly + * used to filter objects by value or by type. + */ + + +import python +import semmle.dataflow.SSA + +/** Holds if `c` is a call to `hasattr(obj, attr)`. */ +predicate hasattr(CallNode c, ControlFlowNode obj, string attr) { + c.getFunction().getNode().(Name).getId() = "hasattr" and + c.getArg(0) = obj and + c.getArg(1).getNode().(StrConst).getText() = attr +} + +/** Holds if `c` is a call to `callable(obj)`. */ +predicate is_callable(CallNode c, ControlFlowNode obj) { + c.getFunction().(NameNode).getId() = "callable" and + obj = c.getArg(0) +} + +/** Holds if `c` is a call to `isinstance(use, cls)`. */ +predicate isinstance(CallNode fc, ControlFlowNode cls, ControlFlowNode use) { + fc.getFunction().(NameNode).getId() = "isinstance" and + cls = fc.getArg(1) and fc.getArg(0) = use +} + +/** Holds if `c` is a test comparing `x` and `y`. `is` is true if the operator is `is` or `==`, it is false if the operator is `is not` or `!=`. */ +predicate equality_test(CompareNode c, ControlFlowNode x, boolean is, ControlFlowNode y) { + exists(Cmpop op | + c.operands(x, op, y) or + c.operands(y, op, x) + | + (is = true and op instanceof Is or + is = false and op instanceof IsNot or + is = true and op instanceof Eq or + is = false and op instanceof NotEq + ) + ) +} + +/** Holds if `c` is a call to `issubclass(use, cls)`. */ +predicate issubclass(CallNode fc, ControlFlowNode cls, ControlFlowNode use) { + fc.getFunction().(NameNode).getId() = "issubclass" and + fc.getArg(0) = use and cls = fc.getArg(1) +} + diff --git a/python/ql/src/semmle/python/pointsto/Final.qll b/python/ql/src/semmle/python/pointsto/Final.qll new file mode 100644 index 000000000000..a1f14b0f87c9 --- /dev/null +++ b/python/ql/src/semmle/python/pointsto/Final.qll @@ -0,0 +1,6 @@ +/* For backwards compatibility */ + +import PointsTo::PointsTo as P + +/** DEPRECATED: Use `PointsTo` instead */ +deprecated module FinalPointsTo = P; \ No newline at end of file diff --git a/python/ql/src/semmle/python/pointsto/MRO.qll b/python/ql/src/semmle/python/pointsto/MRO.qll new file mode 100644 index 000000000000..11ac81b120a7 --- /dev/null +++ b/python/ql/src/semmle/python/pointsto/MRO.qll @@ -0,0 +1,466 @@ +/** Classes and predicates for computing the Method Resolution Order (MRO) of classes. + * Supports both old-style (diamond) inheritance and new-style (C3 linearization) inheritance. + */ + +/* + * Implementation of the C3 linearization algorithm. + * See https://en.wikipedia.org/wiki/C3_linearization + * + * The key operation is merge, which takes a list of lists and produces a list. + * We implement it as the method `ClassListList.merge()` + * + * To support that we need to determine the best candidate to extract from a list of lists, + * implemented as `ClassListList.bestMergeCandidate()` + * + * The following code is designed to implement those operations + * without negation and as efficiently as possible. + */ + +import python +import semmle.python.pointsto.PointsTo + +cached private newtype TClassList = Empty() + or + Cons(ClassObject head, TClassList tail) { + required_cons(head, tail) + } + +/* Keep ClassList finite and as small as possible */ +private predicate required_cons(ClassObject head, ClassList tail) { + tail = merge_of_linearization_of_bases(head) + or + exists(ClassObject cls, int n | + head = cls.getBaseType(n) and tail = bases(cls, n+1) + ) + or + head = theObjectType() and tail = Empty() + or + reverse_step(_, Cons(head, _), tail) + or + exists(ClassListList list | + merge_step(tail, list, _) and + head = list.bestMergeCandidate() + ) + or + exists(ClassList list, int n | + n = list.firstIndex(head) and + tail = list.deduplicate(n+1) + ) + or + exists(ClassListList list, int n | + head = list.getHead().getItem(n) and + tail = flatten_list(list, n+1) + ) + or + tail = list_old_style_base_mros(head).flatten() +} + +/** A list of classes, used to represent the MRO of a class */ +class ClassList extends TClassList { + + string toString() { + result = "[" + this.contents() + "]" + } + + string contents() { + this = Empty() and result = "" + or + exists(ClassObject head | + head = this.getHead() | + this.getTail() = Empty() and result = head.getName() + or + this.getTail() != Empty() and result = head.getName() + ", " + this.getTail().contents() + ) + } + + int length() { + this = Empty() and result = 0 + or + result = this.getTail().length() + 1 + } + + ClassObject getHead() { + this = Cons(result, _) + } + + ClassList getTail() { + this = Cons(_, result) + } + + ClassObject getItem(int n) { + n = 0 and result = this.getHead() + or + result = this.getTail().getItem(n-1) + } + + pragma [inline] + ClassList removeHead(ClassObject cls) { + this.getHead() = cls and result = this.getTail() + or + this.getHead() != cls and result = this + or + this = Empty() and result = Empty() + } + + predicate legalMergeHead(ClassObject cls) { + this.getTail().doesNotContain(cls) + or + this = Empty() + } + + /** Use negative formulation for efficiency */ + predicate contains(ClassObject cls) { + cls = this.getHead() + or + this.getTail().contains(cls) + } + + /** Use negative formulation to avoid negative recursion */ + predicate doesNotContain(ClassObject cls) { + this.relevantForContains(cls) and + cls != this.getHead() and + this.getTail().doesNotContain(cls) + or + this = Empty() + } + + private predicate relevantForContains(ClassObject cls) { + exists(ClassListList list | + list.getItem(_).getHead() = cls and + list.getItem(_) = this + ) + or + exists(ClassList l | + l.relevantForContains(cls) and + this = l.getTail() + ) + } + + ClassObject findDeclaringClass(string name) { + exists(ClassObject head | + head = this.getHead() and + not head = theUnknownType() | + if head.declaresAttribute(name) then + result = head + else + result = this.getTail().findDeclaringClass(name) + ) + } + + Object lookup(string name) { + exists(ClassObject head | + head = this.getHead() and + not head = theUnknownType() | + if head.declaresAttribute(name) then + result = head.declaredAttribute(name) + else + result = this.getTail().lookup(name) + ) + } + + predicate declares(string name) { + this.getHead().declaresAttribute(name) + or + this.getTail().declares(name) + } + + ClassList startingAt(ClassObject cls) { + exists(ClassObject head | + head = this.getHead() | + if head = cls then + result = this + else + result = this.getTail().startingAt(cls) + ) + } + + ClassList deduplicate() { + result = this.deduplicate(0) + } + + /* Helpers for `deduplicate()` */ + + int firstIndex(ClassObject cls) { + result = this.firstIndex(cls, 0) + } + + /* Helper for firstIndex(cls), getting the first index of `cls` where result >= n */ + private int firstIndex(ClassObject cls, int n) { + this.getItem(n) = cls and result = n + or + this.getItem(n) != cls and result = this.firstIndex(cls, n+1) + } + + /** Holds if the class at `n` is a duplicate of an earlier position. */ + private predicate duplicate(int n) { + exists(ClassObject cls | + cls = this.getItem(n) and this.firstIndex(cls) < n + ) + } + + /** Gets a class list which is the de-duplicated form of the list containing elements of + * this list from `n` onwards. + */ + ClassList deduplicate(int n) { + n = this.length() and result = Empty() + or + this.duplicate(n) and result = this.deduplicate(n+1) + or + exists(ClassObject cls | + n = this.firstIndex(cls) and + result = Cons(cls, this.deduplicate(n+1)) + ) + } + + predicate isEmpty() { + this = Empty() + } + + ClassList reverse() { + reverse_step(this, Empty(), result) + } +} + +private newtype TClassListList = + EmptyList() or + ConsList(TClassList head, TClassListList tail) { + required_list(head, tail) + } + +/* Keep ClassListList finite and as small as possible */ +private predicate required_list(ClassList head, ClassListList tail) { + any(ClassListList x).removedClassParts(_, head, tail, _) + or + head = bases(_) and tail = EmptyList() + or + exists(ClassObject cls, int n | + head = new_style_mro(cls.getBaseType(n)) and + tail = list_of_linearization_of_bases_plus_bases(cls, n+1) + ) + or + exists(ClassObject cls, int n | + head = old_style_mro(cls.getBaseType(n)) and + tail = list_old_style_base_mros(cls, n+1) + ) +} + +private class ClassListList extends TClassListList { + + string toString() { + result = "[" + this.contents() + "]" + } + + string contents() { + this = EmptyList() and result = "" + or + exists(ClassList head | + head = this.getHead() | + this.getTail() = EmptyList() and result = head.toString() + or + this.getTail() != EmptyList() and result = head.toString() + ", " + this.getTail().contents() + ) + } + + int length() { + this = EmptyList() and result = 0 + or + result = this.getTail().length() + 1 + } + + ClassList getHead() { + this = ConsList(result, _) + } + + ClassListList getTail() { + this = ConsList(_, result) + } + + ClassList getItem(int n) { + n = 0 and result = this.getHead() + or + result = this.getTail().getItem(n-1) + } + + private ClassObject getAHead() { + result = this.getHead().getHead() + or + result = this.getTail().getAHead() + } + + pragma [nomagic] + ClassList merge() { + exists(ClassList reversed | + merge_step(reversed, EmptyList(), this) and + result = reversed.reverse() + ) + or + this = EmptyList() and result = Empty() + } + + /* Join ordering helper */ + pragma [noinline] + predicate removedClassParts(ClassObject cls, ClassList removed_head, ClassListList removed_tail, int n) { + cls = this.bestMergeCandidate() and n = this.length()-1 and + removed_head = this.getItem(n).removeHead(cls) and removed_tail = EmptyList() + or + exists(ClassList prev_head, ClassListList prev_tail | + this.removedClassParts(cls, prev_head, prev_tail, n+1) and + removed_head = this.getItem(n).removeHead(cls) and + removed_tail = ConsList(prev_head, prev_tail) + ) + } + + ClassListList remove(ClassObject cls) { + exists(ClassList removed_head, ClassListList removed_tail | + this.removedClassParts(cls, removed_head, removed_tail, 0) and + result = ConsList(removed_head, removed_tail) + ) + or + this = EmptyList() and result = EmptyList() + } + + predicate legalMergeCandidate(ClassObject cls, int n) { + cls = this.getAHead() and n = this.length() + or + this.getItem(n).legalMergeHead(cls) and + this.legalMergeCandidate(cls, n+1) + } + + predicate legalMergeCandidate(ClassObject cls) { + this.legalMergeCandidate(cls, 0) + } + + predicate illegalMergeCandidate(ClassObject cls) { + cls = this.getAHead() and + this.getItem(_).getTail().contains(cls) + } + + ClassObject bestMergeCandidate(int n) { + exists(ClassObject head | + head = this.getItem(n).getHead() + | + legalMergeCandidate(head) and result = head + or + illegalMergeCandidate(head) and result = this.bestMergeCandidate(n+1) + ) + } + + ClassObject bestMergeCandidate() { + result = this.bestMergeCandidate(0) + } + + /** Gets a ClassList representing the this list of list flattened into a single list. + * Used for old-style MRO computation. + */ + ClassList flatten() { + this = EmptyList() and result = Empty() + or + result = flatten_list(this, 0) + } + +} + +private ClassList flatten_list(ClassListList list, int n) { + need_flattening(list) and + exists(ClassList head, ClassListList tail | + list = ConsList(head, tail) + | + n = head.length() and result = tail.flatten() + or + result = Cons(head.getItem(n), flatten_list(list, n+1)) + ) +} + +/* Restrict flattening to those lists that need to be flattened */ +private predicate need_flattening(ClassListList list) { + list = list_old_style_base_mros(_) + or + exists(ClassListList toflatten | + need_flattening(toflatten) and + list = toflatten.getTail() + ) +} + +private ClassList bases(ClassObject cls) { + result = bases(cls, 0) +} + +private ClassList bases(ClassObject cls, int n) { + result = Cons(cls.getBaseType(n), bases(cls, n+1)) + or + result = Empty() and n = PointsTo::Types::class_base_count(cls) +} + +private ClassListList list_of_linearization_of_bases_plus_bases(ClassObject cls) { + result = list_of_linearization_of_bases_plus_bases(cls, 0) +} + +private ClassListList list_of_linearization_of_bases_plus_bases(ClassObject cls, int n) { + result = ConsList(bases(cls), EmptyList()) and n = PointsTo::Types::class_base_count(cls) + or + exists(ClassListList partial | + partial = list_of_linearization_of_bases_plus_bases(cls, n+1) and + result = ConsList(new_style_mro(cls.getBaseType(n)), partial) + ) +} + +private ClassList merge_of_linearization_of_bases(ClassObject cls) { + result = list_of_linearization_of_bases_plus_bases(cls).merge() +} + +cached ClassList new_style_mro(ClassObject cls) { + cls = theObjectType() and result = Cons(cls, Empty()) + or + result = Cons(cls, merge_of_linearization_of_bases(cls)) +} + +cached ClassList old_style_mro(ClassObject cls) { + PointsTo::Types::is_new_style_bool(cls) = false and + result = Cons(cls, list_old_style_base_mros(cls).flatten()).(ClassList).deduplicate() +} + +private ClassListList list_old_style_base_mros(ClassObject cls) { + result = list_old_style_base_mros(cls, 0) +} + +pragma [nomagic] +private ClassListList list_old_style_base_mros(ClassObject cls, int n) { + n = PointsTo::Types::class_base_count(cls) and result = EmptyList() + or + result = ConsList(old_style_mro(cls.getBaseType(n)), list_old_style_base_mros(cls, n+1)) +} + +/** Holds if the pair `reversed_mro`, `remaining_list` represents a step in the C3 merge operation + * of computing the C3 linearization of `original`. + */ +private predicate merge_step(ClassList reversed_mro, ClassListList remaining_list, ClassListList original) { + remaining_list = list_of_linearization_of_bases_plus_bases(_) and reversed_mro = Empty() and remaining_list = original + or + /* Removes the best merge candidate from `remaining_list` and prepends it to `reversed_mro` */ + exists(ClassObject head, ClassList prev_reverse_mro, ClassListList prev_list | + merge_step(prev_reverse_mro, prev_list, original) and + head = prev_list.bestMergeCandidate() and + reversed_mro = Cons(head, prev_reverse_mro) and + remaining_list = prev_list.remove(head) + ) + or + merge_step(reversed_mro, ConsList(Empty(), remaining_list), original) +} + +/* Helpers for `ClassList.reverse()` */ + +private predicate needs_reversing(ClassList lst) { + merge_step(lst, EmptyList(), _) + or + lst = Empty() +} + +private predicate reverse_step(ClassList lst, ClassList remainder, ClassList reversed) { + needs_reversing(lst) and remainder = lst and reversed = Empty() + or + exists(ClassObject head, ClassList tail | + reversed = Cons(head, tail) and + reverse_step(lst, Cons(head, remainder), tail) + ) +} + diff --git a/python/ql/src/semmle/python/pointsto/Overview.qll b/python/ql/src/semmle/python/pointsto/Overview.qll new file mode 100644 index 000000000000..f46f83dbb3c0 --- /dev/null +++ b/python/ql/src/semmle/python/pointsto/Overview.qll @@ -0,0 +1,122 @@ +/** + * + * ## Points-to analysis for Python + * + * + * The purpose of points-to analysis is to determine what values a variable might hold at runtime. + * This allows us to write useful queries to check for the misuse of those values. + * In the academic and technical literature, points-to analysis (AKA pointer analysis) attempts to determine which variables can refer to which heap allocated objects. + * From the point of view of Python we can treat all Python objects as "heap allocated objects". + * + * + * The output of the points-to analysis consists of a large set of relations which provide not only points-to information, but call-graph, pruned flow-graph and exception-raising information. + * + * These relations are computed by a large set of mutually recursive predicates which infer the flow of values through the program. + * Our analysis is inter-procedural use contexts to maintain the precision of an intra-procedural analysis. + * + * ### Precision + * + * In conventional points-to, the computed points-to set should be a super-set of the real points-to set (were it possible to determine such a thing). + * However for our purposes we want the points-to set to be a sub-set of the real points-to set. + * This is simply because conventional points-to is used to determine compiler optimisations, so the points-to set needs to be a conservative over-estimate of what is possible. + * We have the opposite concern; we want to eliminate false positives where possible. + * + * This should be born in mind when reading the literature about points-to analysis. In conventional points-to, a precise analysis produces as small a points-to set as possible. + * Our analysis is precise (or very close to it). Instead of seeking to maximise precision, we seek to maximise *recall* and produce as large a points-to set as possible (whilst remaining precise). + * + * When it comes to designing the inference, we always choose precision over recall. + * We want to minimise false positives so it is important to avoid making incorrect inferences, even if it means losing a lot of potential information. + * If a potential new points-to fact would increase the number of values we are able to infer, but decrease precision, then we omit it. + * + * ###Objects + * + * In convention points-to an 'object' is generally considered to be any static instantiation. E.g. in Java this is simply anything looking like `new X(..)`. + * However, in Python as there is no `new` expression we cannot known what is a class merely from the syntax. + * Consequently, we must start with only with the simplest objects and extend to instance creation as we can infer classes. + * + * To perform points-to analysis we start with the set of built-in objects, all literal constants, and class and function definitions. + * From there we can propagate those values. Whenever we see a call `x()` we add a new object if `x` refers to some class. + * + * In the `PointsTo::points_to` relation, the second argument, `Object value` is the "value" referred to by the ControlFlowNode (which will correspond to an rvalue in the source code). + * The set of "values" used will change as the library continues to improve, but currently include the following: + * + * * Classes (both in the source and builtin) + * * Functions (both in the source and builtin) + * * Literal constants defined in the source (string and numbers) + * * Constant objects defined in compiled libraries and the interpreter (None, boolean, strings and numbers) + * * Some calls (many calls are absent as we can infer what the call returns). Consider a call to represent the set of objects that it could return. + * * Some other constructs that might create a new object. + * + * A number of constructs that might create a new object, such as binary operations, are omitted if there is no useful information to can be attached to them and they would just increase the size of the database. + * + * ###Contexts + * + * In order to better handle value tracking in functions, we introduce context to the points-to relation. + * There is one `default` context, equivalent to having no context, a `main` context for scripts and any number of call-site contexts. + * + * Adding context to a conventional points-to analysis can significantly improve its precision. Whereas, for our points-to analysis adding context significantly improves the recall of our analysis. + * The consensus in the academic literature is that "object sensitivity" is superior to "call-site sensitivity". + * However, since we are seeking to maximise not minimise our points-to set, it is entirely possible that the reverse is true for us. + * We use "call-site sensitivity" at the moment, although the exact set of contexts used will change. + * + * ### Points-to analysis over the ESSA dataflow graph + * + * In order to perform points-to analysis on the dataflow graph, we + * need to understand the many implicit "definitions" that occur within Python code. + * + * These are: + * + * 1. Implicit definition as "undefined" for any local or global variable at the start of its scope. + * Many of these will be dead and will be eliminated during construction of the dataflow graph. + * 2. Implicit definition of `__name__`, `__package__` and `__module__` at the start of the relevant scopes. + * 3. Implicit definition of all submodules as global variables at the start of an `__init__` module + * + * In addition, there are the "artificial", data-flow definitions: + * + * 1. Phi functions + * 2. Pi (guard, or filter) functions. + * 3. "Refinements" of a variable. These are not definitions of the variable, but may modify the object referred to by the variable, + * possibly changing some inferred facts about the object. + * 4. Definition of any variable that escapes the scope, at entry, exit and at all call-sites. + * + * As an example, consider: + * ```python + * if a: + * float = "global" + * #float can now be either the class 'float' or the string "global" + * + * class C2: + * if b: + * float = "local" + * float + * + * float #Cannot be "local" + * ``` + * + * Ignoring `__name__` and `__package__`, the data-flow graph looks something like this, noting that there are two variables named "float" + * in the scope `C2`, the local and the global. + * + * ``` + * a_0 = undefined + * b_0 = undefined + * float_0 = undefined + * int_0 = undefined + * float_1 = "global" + * float_2 = phi(float_0, float_1) + * float_3 = float_2 (Definition on entry to C2 for global variable) + * float_4 = undefined (Definition on entry to C2 for local variable) + * float_5 = "local" | + * float_6 = phi(float_4, float_5) | + * float_7 = float_3 (transfer values in global 'float', but not local, back to module scope). + * ``` + * + * ### Implementation + * + * This section is for information purposes only. Any or all details may change without notice. + * + * QL, being based on Datalog, has fixed-point semantics which makes it impossible to make negative statements that are recursive. + * To work around this we need to define many predicates over boolean variables. Suppose we have a predicate with determines whether a test can be true or false at runtime. + * We might naively implement this as `predicate test_is_true(ControlFlowNode test, Context ctx)` but this would lead to negative recursion if we want to know when the test can be false. + * Instead we implement it as `boolean test_result(ControlFlowNode test, Context ctx)` where the absence of a value indicates merely that we do (yet) know what value the test may have. + * + */ diff --git a/python/ql/src/semmle/python/pointsto/PointsTo.qll b/python/ql/src/semmle/python/pointsto/PointsTo.qll new file mode 100644 index 000000000000..284e5ccb5e70 --- /dev/null +++ b/python/ql/src/semmle/python/pointsto/PointsTo.qll @@ -0,0 +1,2857 @@ +/** + * Part of the combined points-to, call-graph and type-inference library. + * The main relation `points_to(node, context, object, cls, origin)` relates a control flow node + * to the possible objects it points-to the inferred types of those objects and the 'origin' + * of those objects. The 'origin' is the point in source code that the object can be traced + * back to. + * + * The predicates provided are not intended to be used directly (although they are available to the advanced user), but are exposed in the user API as methods on key classes. + * + * For example, the most important predicate in the points-to relation is: + * ```ql + * predicate PointsTo::points_to(ControlFlowNode f, PointsToContext ctx, Object value, ClassObject cls, ControlFlowNode origin) + * ``` + * Where `f` is the control flow node representing something that might hold a value. `ctx` is the context in which `f` "points-to" `value` and may be "general" or from a specific call-site. + * `value` is a static approximation to a value, such as a number, a class, or an object instantiation. + * `cls` is the class of this value if known, or `theUnknownType()` which is an internal `ClassObject` and should not be exposed to the general QL user. + * `origin` is the point in the source from where `value` originates and is useful when presenting query results. + * + * The `PointsTo::points_to` relation is exposed at the user API level as + * ```ql + * ControlFlowNode.refersTo(Context context, Object value, ClassObject cls, ControlFlowNode origin) + * ``` + * + */ + +import python +private import PointsToContext +private import Base +private import semmle.python.types.Extensions +private import Filters as BaseFilters +import semmle.dataflow.SSA +private import MRO + +/** Get a `ControlFlowNode` from an object or `here`. + * If the object is a ControlFlowNode then use that, otherwise fall back on `here` + */ +pragma[inline] +private ControlFlowNode origin_from_object_or_here(ObjectOrCfg object, ControlFlowNode here) { + result = object + or + not object instanceof ControlFlowNode and result = here +} + +module PointsTo { + + cached module API { + + /** INTERNAL -- Use `FunctionObject.getACall()`. + * + * Gets a call to `func` with the given context. */ + cached CallNode get_a_call(FunctionObject func, PointsToContext context) { + function_call(func, context, result) + or + method_call(func, context, result) + } + + /** INTERNAL -- Use `FunctionObject.getAFunctionCall()`. + * + * Holds if `call` is a function call to `func` with the given context. */ + cached predicate function_call(FunctionObject func, PointsToContext context, CallNode call) { + points_to(call.getFunction(), context, func, _, _) + } + + /** INTERNAL -- Use `FunctionObject.getAMethodCall()`. + * + * Holds if `call` is a method call to `func` with the given context. */ + cached predicate method_call(FunctionObject func, PointsToContext context, CallNode call) { + Calls::plain_method_call(func, context, call) + or + Calls::super_method_call(context, call, _, func) + or + class_method_call(_, _, func, context, call) + } + + /** INTERNAL -- Use `ClassMethod.getACall()` instead */ + cached predicate class_method_call(Object cls_method, ControlFlowNode attr, FunctionObject func, PointsToContext context, CallNode call) { + exists(ClassObject cls, string name | + attr = call.getFunction() and + Types::class_attribute_lookup(cls, name, cls_method, _, _) | + Calls::receiver_type_for(cls, name, attr, context) + or + points_to(attr.(AttrNode).getObject(name), context, cls, _, _) + ) and + class_method(cls_method, func) + } + + /** INTERNAL -- Use `ClassMethod` instead */ + cached predicate class_method(Object cls_method, FunctionObject method) { + decorator_call(cls_method, theClassMethodType(), method) + } + + pragma [nomagic] + private predicate decorator_call(Object method, ClassObject decorator, FunctionObject func) { + exists(CallNode f, PointsToContext imp | + method = f and imp.isImport() and + points_to(f.getFunction(), imp, decorator, _, _) and + points_to(f.getArg(0), imp, func, _, _) + ) + } + + /** INTERNAL -- Use `f.refersTo(value, cls, origin)` instead. */ + cached predicate points_to(ControlFlowNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + points_to_candidate(f, context, value, cls, origin) and + Layer::reachableBlock(f.getBasicBlock(), context) + } + + /** Gets the value that `expr` evaluates to (when converted to a boolean) when `use` refers to `(val, cls, origin)` + * and `expr` is a test (a branch) and contains `use`. */ + cached boolean test_evaluates_boolean(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, Object val, ClassObject cls, ControlFlowNode origin) { + test_contains(expr, use) and + result = Filters::evaluates(expr, use, context, val, cls, origin) + } + + /** INTERNAL -- Do not use. + * + * Holds if `package.name` points to `(value, cls, origin)`, where `package` is a package object. */ + cached predicate package_attribute_points_to(PackageObject package, string name, Object value, ClassObject cls, ControlFlowNode origin) { + py_module_attributes(package.getInitModule().getModule(), name, value, cls, origin) + or + exists(Module init | + init = package.getInitModule().getModule() | + not exists(Variable v | v.getScope() = init | v.getId() = name or v.getId() = "*") + or + exists(EssaVariable v, PointsToContext imp | + v.getScope() = init and v.getName() = "*" and v.getAUse() = init.getANormalExit() | + SSA::ssa_variable_named_attribute_points_to(v, imp, name, undefinedVariable(), _, _) and + imp.isImport() + ) + ) and explicitly_imported(value) and + value = package.submodule(name) and cls = theModuleType() and origin = value + } + + /** INTERNAL -- `Use m.attributeRefersTo(name, obj, origin)` instead. + * + * Holds if `m.name` points to `(value, cls, origin)`, where `m` is a (source) module. */ + cached predicate py_module_attributes(Module m, string name, Object obj, ClassObject cls, ControlFlowNode origin) { + exists(EssaVariable var, ControlFlowNode exit, ObjectOrCfg orig, PointsToContext imp | + exit = m.getANormalExit() and var.getAUse() = exit and + var.getSourceVariable().getName() = name and + ssa_variable_points_to(var, imp, obj, cls, orig) and + imp.isImport() and + not obj = undefinedVariable() | + origin = origin_from_object_or_here(orig, exit) + ) + or + not exists(EssaVariable var | var.getAUse() = m.getANormalExit() and var.getSourceVariable().getName() = name) and + exists(EssaVariable var, PointsToContext imp | + var.getAUse() = m.getANormalExit() and var.getSourceVariable().getName() = "*" | + SSA::ssa_variable_named_attribute_points_to(var, imp, name, obj, cls, origin) and + imp.isImport() and not obj = undefinedVariable() + ) + } + + /** INTERNAL -- Use `ModuleObject.hasAttribute(name)` + * + * Whether the module defines name. */ + cached predicate module_defines_name(Module mod, string name) { + module_defines_name_boolean(mod, name) = true + } + + /** INTERNAL -- Use `Version.isTrue()` instead. + * + * Holds if `cmp` points to a test on version that is `value`. + * For example, if `cmp` is `sys.version[0] < "3"` then for, Python 2, `value` would be `true`. */ + cached predicate version_const(CompareNode cmp, PointsToContext context, boolean value) { + exists(ControlFlowNode fv, ControlFlowNode fc, Object val | + comparison(cmp, fv, fc, _) and + points_to(cmp, context, val, _, _) and + value = val.booleanValue() + | + sys_version_info_slice(fv, context, _) + or + sys_version_info_index(fv, context, _, _) + or + sys_version_string_char0(fv, context, _, _) + or + points_to(fv, context, theSysHexVersionNumber(), _, _) + ) + or + value = version_tuple_compare(cmp, context).booleanValue() + } + + /** INTERNAL -- Use `FunctionObject.getArgumentForCall(call, position)` instead. */ + cached ControlFlowNode get_positional_argument_for_call(FunctionObject func, PointsToContext context, CallNode call, int position) { + result = Calls::get_argument_for_call_by_position(func, context, call, position) + or + exists(string name | + result = Calls::get_argument_for_call_by_name(func, context, call, name) and + func.getFunction().getArg(position).asName().getId() = name + ) + } + + /** INTERNAL -- Use `FunctionObject.getNamedArgumentForCall(call, name)` instead. */ + cached ControlFlowNode get_named_argument_for_call(FunctionObject func, PointsToContext context, CallNode call, string name) { + ( + result = Calls::get_argument_for_call_by_name(func, context, call, name) + or + exists(int position | + result = Calls::get_argument_for_call_by_position(func, context, call, position) and + func.getFunction().getArg(position).asName().getId() = name + ) + ) + } + + /** INTERNAL -- Use `FunctionObject.neverReturns()` instead. + * Whether function `func` never returns. Slightly conservative approximation, this predicate may be false + * for a function that can never return. */ + cached predicate function_never_returns(FunctionObject func) { + /* A Python function never returns if it has no normal exits that are not dominated by a + * call to a function which itself never returns. + */ + function_can_never_return(func) + or + exists(Function f | + f = func.getFunction() + | + forall(BasicBlock exit | + exit = f.getANormalExit().getBasicBlock() | + exists(FunctionObject callee, BasicBlock call | + get_a_call(callee, _).getBasicBlock() = call and + function_never_returns(callee) and + call.dominates(exit) + ) + ) + ) + } + + /** INTERNAL -- Use `m.importedAs(name)` instead. + * + * Holds if `import name` will import the module `m`. */ + cached predicate module_imported_as(ModuleObject m, string name) { + /* Normal imports */ + m.getName() = name + or + /* sys.modules['name'] = m */ + exists(ControlFlowNode sys_modules_flow, ControlFlowNode n, ControlFlowNode mod | + /* Use previous points-to here to avoid slowing down the recursion too much */ + exists(SubscriptNode sub, Object sys_modules | + sub.getValue() = sys_modules_flow and + points_to(sys_modules_flow, _, sys_modules, _, _) and + builtin_module_attribute(theSysModuleObject(), "modules", sys_modules, _) and + sub.getIndex() = n and + n.getNode().(StrConst).getText() = name and + sub.(DefinitionNode).getValue() = mod and + points_to(mod, _, m, _, _) + ) + ) + } + + /** Holds if `call` is of the form `getattr(arg, "name")`. */ + cached predicate getattr(CallNode call, ControlFlowNode arg, string name) { + points_to(call.getFunction(), _, builtin_object("getattr"), _, _) and + call.getArg(1).getNode().(StrConst).getText() = name and + arg = call.getArg(0) + } + + /** Holds if `f` is the instantiation of an object, `cls(...)`. */ + cached predicate instantiation(CallNode f, PointsToContext context, ClassObject cls) { + points_to(f.getFunction(), context, cls, _, _) and + not cls = theTypeType() and + Types::callToClassWillReturnInstance(cls) + } + + /** Holds if `var` refers to `(value, cls, origin)` given the context `context`. */ + cached predicate ssa_variable_points_to(EssaVariable var, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + SSA::ssa_definition_points_to(var.getDefinition(), context, value, cls, origin) + } + + + } + + predicate name_maybe_imported_from(ModuleObject mod, string name) { + exists(Module m, ImportStar s | + has_import_star(m, s, mod) | + exists(Variable var | name = var.getId() and var.getScope() = s.getScope()) + or + exists(ModuleObject other | + name_maybe_imported_from(other, name) and other.getModule() = m + ) + ) + or + exists(ImportMemberNode imp | + points_to(imp.getModule(name), _, mod, _, _) + ) + or + exists(PackageObject pack | + pack.getInitModule() = mod | + name_maybe_imported_from(pack, name) + ) + or + exists(mod.(PackageObject).submodule(name)) + or + exists(PackageObject package | + package.getInitModule() = mod and + exists(package.submodule(name)) + ) + or + module_exports(mod, name) + or + name = "__name__" + } + + private boolean module_defines_name_boolean(Module m, string name) { + exists(ModuleObject mod | + m = mod.getModule() | + exists(SsaVariable var | name = var.getId() and var.getAUse() = m.getANormalExit()) and result = true + or + name_maybe_imported_from(mod, name) and not any(ImportStar i).getScope() = m and result = false and + not exists(SsaVariable var | name = var.getId() and var.getAUse() = m.getANormalExit()) and + not exists(PackageObject pack | + pack.getInitModule() = mod and + exists(pack.submodule(name)) + ) + or + exists(Object obj | + not obj = undefinedVariable() and + py_module_attributes(mod.getModule(), name, obj, _, _) + ) and result = true + or + exists(ImportStarNode isn, ModuleObject imported | + isn.getScope() = m and + points_to(isn.getModule(), _, imported, _, _) and + module_exports(imported, name) + ) and result = true + ) + or + name = "__name__" and result = true + } + + private boolean py_module_exports_boolean(ModuleObject mod, string name) { + exists(Module m | + m = mod.getModule() | + /* Explicitly declared in __all__ */ + m.declaredInAll(name) and result = true + or + /* No __all__ and name is defined and public */ + not m.declaredInAll(_) and name.charAt(0) != "_" and + result = module_defines_name_boolean(m, name) + or + /* May be imported from this module, but not declared in __all__ */ + name_maybe_imported_from(mod, name) and m.declaredInAll(_) and not m.declaredInAll(name) and + result = false + ) + } + + private boolean package_exports_boolean(PackageObject pack, string name) { + explicitly_imported(pack.submodule(name)) and + ( + not exists(pack.getInitModule()) + or + exists(ModuleObject init | + pack.getInitModule() = init | + not init.getModule().declaredInAll(_) and name.charAt(0) != "_" + ) + ) and result = true + or + result = module_exports_boolean(pack.getInitModule(), name) + } + + /** INTERNAL -- Use `m.exports(name)` instead. */ + cached predicate module_exports(ModuleObject mod, string name) { + module_exports_boolean(mod, name) = true + } + + private boolean module_exports_boolean(ModuleObject mod, string name) { + py_cmembers_versioned(mod, name, _, major_version().toString()) and + name.charAt(0) != "_" and result = true + or + result = package_exports_boolean(mod, name) + or + result = py_module_exports_boolean(mod, name) + } + + /** Predicates in this layer need to visible to the next layer, but not otherwise */ + private module Layer { + + /* Holds if BasicBlock `b` is reachable, given the context `context`. */ + predicate reachableBlock(BasicBlock b, PointsToContext context) { + context.appliesToScope(b.getScope()) and + forall(ConditionBlock guard | + guard.controls(b, _) | + exists(Object value | + points_to(guard.getLastNode(), context, value, _, _) + | + guard.controls(b, true) and not value.booleanValue() = false + or + guard.controls(b, false) and not value.booleanValue() = true + ) + or + /* Assume the true edge of an assert is reachable (except for assert 0/False) */ + guard.controls(b, true) and + exists(Assert a, Expr test | + a.getTest() = test and + guard.getLastNode().getNode() = test and + not test instanceof ImmutableLiteral + ) + ) + } + + /* Holds if the edge `pred` -> `succ` is reachable, given the context `context`. + */ + predicate controlledReachableEdge(BasicBlock pred, BasicBlock succ, PointsToContext context) { + exists(ConditionBlock guard, Object value | + points_to(guard.getLastNode(), context, value, _, _) + | + guard.controlsEdge(pred, succ, true) and not value.booleanValue() = false + or + guard.controlsEdge(pred, succ, false) and not value.booleanValue() = true + ) + } + + /** Holds if `mod.name` points to `(value, cls, origin)`, where `mod` is a module object. */ + predicate module_attribute_points_to(ModuleObject mod, string name, Object value, ClassObject cls, ObjectOrCfg origin) { + py_module_attributes(mod.getModule(), name, value, cls, origin) + or + package_attribute_points_to(mod, name, value, cls, origin) + or + builtin_module_attribute(mod, name, value, cls) and origin = value + } + + } + + import API + + /* Holds if `f` points to a test on the OS that is `value`. + * For example, if `f` is `sys.platform == "win32"` then, for Windows, `value` would be `true`. + */ + private predicate os_const(ControlFlowNode f, PointsToContext context, boolean value) { + exists(string os | + os_test(f, os, context) | + value = true and py_flags_versioned("sys.platform", os, major_version().toString()) + or + value = false and not py_flags_versioned("sys.platform", os, major_version().toString()) + ) + } + + /** Points-to before pruning. */ + pragma [nomagic] + private predicate points_to_candidate(ControlFlowNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + simple_points_to(f, value, cls, origin) and context.appliesToScope(f.getScope()) + or + f.isClass() and value = f and origin = f and context.appliesToScope(f.getScope()) and + cls = Types::class_get_meta_class(value) + or + exists(boolean b | + os_const(f, context, b) + | + value = theTrueObject() and b = true + or + value = theFalseObject() and b = false + ) and cls = theBoolType() and origin = f + or + import_points_to(f, value, origin) and cls = theModuleType() and context.appliesToScope(f.getScope()) + or + attribute_load_points_to(f, context, value, cls, origin) + or + getattr_points_to(f, context, value, cls, origin) + or + if_exp_points_to(f, context, value, cls, origin) + or + from_import_points_to(f, context, value, cls, origin) + or + use_points_to(f, context, value, cls, origin) + or + def_points_to(f, context, value, cls, origin) + or + Calls::call_points_to(f, context, value, cls, origin) + or + subscript_points_to(f, context, value, cls, origin) + or + sys_version_info_slice(f, context, cls) and value = theSysVersionInfoTuple() and origin = f + or + sys_version_info_index(f, context, value, cls) and origin = f + or + sys_version_string_char0(f, context, value, cls) and origin = f + or + six_metaclass_points_to(f, context, value, cls, origin) + or + binary_expr_points_to(f, context, value, cls, origin) + or + compare_expr_points_to(f, context, value, cls, origin) + or + not_points_to(f, context, value, cls, origin) + or + value.(SuperCall).instantiation(context, f) and f = origin and cls = theSuperType() + or + value.(SuperBoundMethod).instantiation(context, f) and f = origin and cls = theBoundMethodType() + or + exists(boolean b | + b = Filters::evaluates_boolean(f, _, context, _, _, _) + | + value = theTrueObject() and b = true + or + value = theFalseObject() and b = false + ) and cls = theBoolType() and origin = f + or + f.(CustomPointsToFact).pointsTo(context, value, cls, origin) + } + + /** The ESSA variable with fast-local lookup (LOAD_FAST bytecode). */ + private EssaVariable fast_local_variable(NameNode n) { + n.isLoad() and + result.getASourceUse() = n and + result.getSourceVariable() instanceof FastLocalVariable + } + + /** The ESSA variable with name-local lookup (LOAD_NAME bytecode). */ + private EssaVariable name_local_variable(NameNode n) { + n.isLoad() and + result.getASourceUse() = n and + result.getSourceVariable() instanceof NameLocalVariable + } + + /** The ESSA variable for the global variable lookup. */ + private EssaVariable global_variable(NameNode n) { + n.isLoad() and + result.getASourceUse() = n and + result.getSourceVariable() instanceof GlobalVariable + } + + private predicate use_points_to_maybe_origin(NameNode f, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin_or_obj) { + ssa_variable_points_to(fast_local_variable(f), context, value, cls, origin_or_obj) + or + name_lookup_points_to_maybe_origin(f, context, value, cls, origin_or_obj) + or + not exists(fast_local_variable(f)) and not exists(name_local_variable(f)) and + global_lookup_points_to_maybe_origin(f, context, value, cls, origin_or_obj) + } + + pragma [noinline] + private predicate local_variable_undefined(NameNode f, PointsToContext context) { + ssa_variable_points_to(name_local_variable(f), context, undefinedVariable(), _, _) + } + + private predicate name_lookup_points_to_maybe_origin(NameNode f, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin_or_obj) { + exists(EssaVariable var | var = name_local_variable(f) | + ssa_variable_points_to(var, context, value, cls, origin_or_obj) + ) + or + local_variable_undefined(f, context) and + global_lookup_points_to_maybe_origin(f, context, value, cls, origin_or_obj) + } + + private predicate global_lookup_points_to_maybe_origin(NameNode f, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin_or_obj) { + ssa_variable_points_to(global_variable(f), context, value, cls, origin_or_obj) + or + ssa_variable_points_to(global_variable(f), context, undefinedVariable(), _, _) and + potential_builtin_points_to(f, value, cls, origin_or_obj) + or + not exists(global_variable(f)) and context.appliesToScope(f.getScope()) and + potential_builtin_points_to(f, value, cls, origin_or_obj) + } + + /** Gets an object pointed to by a use (of a variable). */ + private predicate use_points_to(NameNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(ObjectOrCfg origin_or_obj | + not value = undefinedVariable() and + use_points_to_maybe_origin(f, context, value, cls, origin_or_obj) | + origin = origin_from_object_or_here(origin_or_obj, f) + ) + } + + /** Gets an object pointed to by the definition of an ESSA variable. */ + private predicate def_points_to(DefinitionNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + points_to(f.getValue(), context, value, cls, origin) + } + + /** Holds if `f` points to `@six.add_metaclass(cls)\nclass ...`. */ + private predicate six_metaclass_points_to(ControlFlowNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(ControlFlowNode meta | + Types::six_add_metaclass(f, value, meta) and + points_to(meta, context, cls, _, _) + ) and + origin = value + } + + /** Holds if `obj.name` points to `(value, cls, orig)`. */ + pragma [noinline] + private predicate class_or_module_attribute(Object obj, string name, Object value, ClassObject cls, ObjectOrCfg orig) { + /* Normal class attributes */ + Types::class_attribute_lookup(obj, name, value, cls, orig) and not cls = theStaticMethodType() and not cls = theClassMethodType() + or + /* Static methods of the class */ + exists(CallNode sm | Types::class_attribute_lookup(obj, name, sm, theStaticMethodType(), _) and sm.getArg(0) = value and cls = thePyFunctionType() and orig = value) + or + /* Module attributes */ + Layer::module_attribute_points_to(obj, name, value, cls, orig) + } + + /** Holds if `f` points to `(value, cls, origin)` where `f` is an instance attribute, `x.attr`. */ + pragma [nomagic] + private predicate instance_attribute_load_points_to(AttrNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + f.isLoad() and + exists(string name | + named_attribute_points_to(f.getObject(name), context, name, value, cls, origin) + or + /* Static methods on the class of the instance */ + exists(CallNode sm, ClassObject icls | + points_to(f.getObject(name), context, _, icls, _) and + Types::class_attribute_lookup(icls, name, sm, theStaticMethodType(), _) and sm.getArg(0) = value and cls = thePyFunctionType() and origin = value + ) + or + /* Unknown instance attributes */ + exists(Object x, ClassObject icls, ControlFlowNode obj_node | + obj_node = f.getObject(name) and + not obj_node.(NameNode).isSelf() and + points_to(obj_node, context, x, icls, _) and + (not x instanceof ModuleObject and not x instanceof ClassObject) and + not icls.isBuiltin() and + Types::class_has_attribute_bool(icls, name) = false and + value = unknownValue() and cls = theUnknownType() and origin = f + ) + ) + } + + pragma[noinline] + private predicate receiver_object(AttrNode f, PointsToContext context, Object cls_or_mod, string name) { + f.isLoad() and + exists(ControlFlowNode fval| + fval = f.getObject(name) and + points_to(fval, context, cls_or_mod, _, _) | + cls_or_mod instanceof ClassObject or + cls_or_mod instanceof ModuleObject + ) + } + + /** Holds if `f` is an attribute `x.attr` and points to `(value, cls, origin)`. */ + private predicate attribute_load_points_to(AttrNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + instance_attribute_load_points_to(f, context, value, cls, origin) + or + exists(Object cls_or_mod, string name, ObjectOrCfg orig | + receiver_object(f, context, cls_or_mod, name) and + class_or_module_attribute(cls_or_mod, name, value, cls, orig) and + origin = origin_from_object_or_here(orig, f) + ) + or + points_to(f.getObject(), context, unknownValue(), theUnknownType(), origin) and value = unknownValue() and cls = theUnknownType() + } + + /** Holds if `f` is an expression node `tval if cond else fval` and points to `(value, cls, origin)`. */ + private predicate if_exp_points_to(IfExprNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + points_to(f.getAnOperand(), context, value, cls, origin) + } + + /** Holds if `f` is an import expression, `import mod` and points to `(value, cls, origin)`. */ + private predicate import_points_to(ControlFlowNode f, ModuleObject value, ControlFlowNode origin) { + exists(string name, ImportExpr i | + i.getAFlowNode() = f and i.getImportedModuleName() = name and + module_imported_as(value, name) and + origin = f + ) + } + + /** Holds if `f` is a "from import" expression, `from mod import x` and points to `(value, cls, origin)`. */ + pragma [nomagic] + private predicate from_import_points_to(ImportMemberNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(EssaVariable var, ObjectOrCfg orig | + live_import_from_dot_in_init(f, var) and + ssa_variable_points_to(var, context, value, cls, orig) and + origin = origin_from_object_or_here(orig, f) + ) + or + not live_import_from_dot_in_init(f, _) and + exists(string name, ModuleObject mod | + points_to(f.getModule(name), context, mod, _, _) | + exists(ObjectOrCfg orig | + Layer::module_attribute_points_to(mod, name, value, cls, orig) and + origin = origin_from_object_or_here(orig, f) + ) + ) + } + + /** Holds if `f` is of the form `getattr(x, "name")` and x.name points to `(value, cls, origin)`. */ + private predicate getattr_points_to(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(ControlFlowNode arg, string name | + named_attribute_points_to(arg, context, name, value, cls, origin) and + getattr(f, arg, name) + ) + } + + /** Whether the module is explicitly imported somewhere. */ + private predicate explicitly_imported(ModuleObject mod) { + exists(ImportExpr ie | module_imported_as(mod, ie.getAnImportedModuleName())) + or + exists(ImportMember im | module_imported_as(mod, im.getImportedModuleName())) + } + + /** Holds if an import star exists in the module m that imports the module `imported_module`, such that the flow from the import reaches the module exit. */ + private predicate has_import_star(Module m, ImportStar im, ModuleObject imported_module) { + exists(string name | + module_imported_as(imported_module, name) and + name = im.getImportedModuleName() and + im.getScope() = m and + im.getAFlowNode().getBasicBlock().reachesExit() + ) + } + + /** Track bitwise expressions so we can handle integer flags and enums. + * Tracking too many binary expressions is likely to kill performance. + */ + private predicate binary_expr_points_to(BinaryExprNode b, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + cls = theIntType() and + exists(ControlFlowNode left, ControlFlowNode right | + bitwise_expression_node(b, left, right) and + points_to(left, context, _, cls, _) and + points_to(right, context, _, cls, _) + ) and + value = origin and origin = b + } + + pragma [noinline] + private predicate incomparable_values(CompareNode cmp, PointsToContext context) { + exists(ControlFlowNode left, ControlFlowNode right | + cmp.operands(left, _, right) and + exists(Object lobj, Object robj | + points_to(left, context, lobj, _, _) and + points_to(right, context, robj, _, _) | + not Filters::comparable_value(lobj) + or + not Filters::comparable_value(robj) + ) + ) + } + + pragma [noinline] + private Object in_tuple(CompareNode cmp, PointsToContext context) { + exists(ControlFlowNode left, ControlFlowNode right | + cmp.operands(left, any(In i), right) and + exists(Object lobj, TupleObject tuple | + points_to(left, context, lobj, _, _) and + points_to(right, context, tuple, _, _) + | + lobj = tuple.getBuiltinElement(_) and result = theTrueObject() + or + not lobj = tuple.getBuiltinElement(_) and result = theFalseObject() + ) + ) + } + + pragma [noinline] + private predicate const_compare(CompareNode cmp, PointsToContext context, int comp, boolean strict) { + exists(ControlFlowNode left, ControlFlowNode right | + inequality(cmp, left, right, strict) and + ( + exists(NumericObject n1, NumericObject n2 | + points_to(left, context, n1, _, _) and + points_to(right, context, n2, _, _) and + comp = int_compare(n1, n2) + ) + or + exists(StringObject s1, StringObject s2| + points_to(left, context, s1, _, _) and + points_to(right, context, s2, _, _) and + comp = string_compare(s1, s2) + ) + ) + ) + } + + pragma [noinline] + private Object version_tuple_compare(CompareNode cmp, PointsToContext context) { + exists(ControlFlowNode lesser, ControlFlowNode greater, boolean strict | + inequality(cmp, lesser, greater, strict) and + exists(TupleObject tuple, int comp | + points_to(lesser, context, tuple, _, _) and + points_to(greater, context, theSysVersionInfoTuple(), _, _) and + comp = version_tuple_compare(tuple) + or + points_to(lesser, context, theSysVersionInfoTuple(), _, _) and + points_to(greater, context, tuple, _, _) and + comp = version_tuple_compare(tuple)*-1 + | + comp = -1 and result = theTrueObject() + or + comp = 0 and strict = false and result = theTrueObject() + or + comp = 0 and strict = true and result = theFalseObject() + or + comp = 1 and result = theFalseObject() + ) + ) + } +/* + pragma [noinline] + private Object version_hex_compare(CompareNode cmp, PointsToContext context) { + exists(ControlFlowNode lesser, ControlFlowNode greater, boolean strict | + inequality(cmp, lesser, greater, strict) and + exists(TupleObject tuple, int comp | + points_to(lesser, context, tuple, _, _) and + points_to(greater, context, theSysHexVersionNumber(), _, _) and + comp = version_tuple_compare(tuple) + or + points_to(lesser, context, theSysHexVersionNumber(), _, _) and + points_to(greater, context, tuple, _, _) and + comp = version_hex_compare(tuple)*-1 + | + comp = -1 and result = theTrueObject() + or + comp = 0 and strict = false and result = theTrueObject() + or + comp = 0 and strict = true and result = theFalseObject() + or + comp = 1 and result = theFalseObject() + ) + ) + } +*/ + private predicate compare_expr_points_to(CompareNode cmp, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + equality_expr_points_to(cmp, context, value, cls, origin) + or + cls = theBoolType() and origin = cmp and + ( + incomparable_values(cmp, context) and + (value = theFalseObject() or value = theTrueObject()) + or + value = in_tuple(cmp, context) + or + exists(int comp, boolean strict | + const_compare(cmp, context, comp, strict) + | + comp = -1 and value = theTrueObject() + or + comp = 0 and strict = false and value = theTrueObject() + or + comp = 0 and strict = true and value = theFalseObject() + or + comp = 1 and value = theFalseObject() + ) + or + value = version_tuple_compare(cmp, context) + ) + } + + pragma[inline] + private int int_compare(NumericObject n1, NumericObject n2) { + exists(int i1, int i2 | + i1 = n1.intValue() and i2 = n2.intValue() | + i1 = i2 and result = 0 + or + i1 < i2 and result = -1 + or + i1 > i2 and result = 1 + ) + } + + pragma[inline] + private int string_compare(StringObject s1, StringObject s2) { + exists(string a, string b | + a = s1.getText() and b = s2.getText() | + a = b and result = 0 + or + a < b and result = -1 + or + a > b and result = 1 + ) + } + + private predicate not_points_to(UnaryExprNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + f.getNode().getOp() instanceof Not and + cls = theBoolType() and origin = f and + exists(Object operand | + points_to(f.getOperand(), context, operand, _, _) + | + not operand.booleanValue() = true and value = theTrueObject() + or + not operand.booleanValue() = false and value = theFalseObject() + ) + } + + private predicate equality_expr_points_to(CompareNode cmp, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + cls = theBoolType() and origin = cmp and + exists(ControlFlowNode x, ControlFlowNode y, Object xobj, Object yobj, boolean is | + BaseFilters::equality_test(cmp, x, is, y) and + points_to(x, context, xobj, _, _) and + points_to(y, context, yobj, _, _) and + Filters::equatable_value(xobj) and Filters::equatable_value(yobj) + | + xobj = yobj and is = true and value = theTrueObject() + or + xobj != yobj and is = true and value = theFalseObject() + or + xobj = yobj and is = false and value = theFalseObject() + or + xobj != yobj and is = false and value = theTrueObject() + ) + } + + private predicate subscript_points_to(SubscriptNode sub, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(Object unknownCollection | + varargs_points_to(unknownCollection, _) or + kwargs_points_to(unknownCollection, _) + | + sub.isLoad() and + points_to(sub.getValue(), context, unknownCollection, _, _) and + value = unknownValue() and cls = theUnknownType() and origin = sub + ) + or + points_to(sub.getValue(), context, unknownValue(), _, _) and + value = unknownValue() and cls = theUnknownType() and origin = sub + } + + /* ************** + * VERSION INFO * + ****************/ + + /** Holds if `s` points to `sys.version_info[0]`. */ + private predicate sys_version_info_index(SubscriptNode s, PointsToContext context, NumericObject value, ClassObject cls) { + points_to(s.getValue(), context, theSysVersionInfoTuple(), _, _) and + exists(NumericObject zero | + zero.intValue() = 0 | + points_to(s.getIndex(), context, zero, _, _) + ) and + value.intValue() = major_version() and + cls = theIntType() + } + + /** Holds if `s` points to `sys.version_info[:x]` or `sys.version_info[:]`. */ + private predicate sys_version_info_slice(SubscriptNode s, PointsToContext context, ClassObject cls) { + points_to(s.getValue(), context, theSysVersionInfoTuple(), cls, _) and + exists(Slice index | index = s.getIndex().getNode() | + not exists(index.getStart()) + ) + } + + /** Holds if `s` points to `sys.version[0]`. */ + private predicate sys_version_string_char0(SubscriptNode s, PointsToContext context, Object value, ClassObject cls) { + points_to(s.getValue(), context, theSysVersionString(), cls, _) and + exists(NumericObject zero | + zero.intValue() = 0 | + points_to(s.getIndex(), context, zero, _, _) + ) + and + value = object_for_string(major_version().toString()) + } + + /* Version tests. Ignore micro and release parts. Treat major, minor as a single version major*10+minor + * Currently cover versions 0.9 to 4.0 + */ + + /** Helper for `version_const`. */ + private predicate comparison(CompareNode cmp, ControlFlowNode fv, ControlFlowNode fc, string opname) { + exists(Cmpop op | + cmp.operands(fv, op, fc) and opname = op.getSymbol() + or + cmp.operands(fc, op, fv) and opname = reversed(op) + ) + } + + /** Helper for `version_const`. */ + private predicate inequality(CompareNode cmp, ControlFlowNode lesser, ControlFlowNode greater, boolean strict) { + exists(Cmpop op | + cmp.operands(lesser, op, greater) and op.getSymbol() = "<" and strict = true + or + cmp.operands(lesser, op, greater) and op.getSymbol() = "<=" and strict = false + or + cmp.operands(greater, op, lesser) and op.getSymbol() = ">" and strict = true + or + cmp.operands(greater, op, lesser) and op.getSymbol() = ">=" and strict = false + ) + } + + /** Holds if `f` is a test for the O/S. */ + private predicate os_test(ControlFlowNode f, string os, PointsToContext context) { + exists(ControlFlowNode c | + os_compare(c, os) and + points_to(f, context, _, _, c) + ) + } + + predicate named_attribute_points_to(ControlFlowNode f, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + exists(EssaVariable var | + var.getAUse() = f | + SSA::ssa_variable_named_attribute_points_to(var, context, name, value, cls, origin) + ) + or + exists(ClassObject c, EssaVariable self, Function init | + instantiation(f, context, c) and + init = c.getPyClass().getInitMethod() and + self.getAUse() = init.getANormalExit() and + SSA::ssa_variable_named_attribute_points_to(self, context, name, value, cls, origin) + ) + } + + private module Calls { + + /** Holds if `f` is a call to type() with a single argument `arg` */ + private predicate call_to_type(CallNode f, ControlFlowNode arg, PointsToContext context) { + points_to(f.getFunction(), context, theTypeType(), _, _) and not exists(f.getArg(1)) and arg = f.getArg(0) + } + + pragma [noinline] + predicate call_to_type_known_python_class_points_to(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(ControlFlowNode arg | + call_to_type(f, arg, context) and + points_to(arg, context, _, value, _) + ) and + origin.getNode() = value.getOrigin() and + cls = theTypeType() + } + + pragma [noinline] + predicate call_to_type_known_builtin_class_points_to(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(ControlFlowNode arg | + call_to_type(f, arg, context) | + points_to(arg, context, _, value, _) + ) and + not exists(value.getOrigin()) and + origin = f and cls = theTypeType() + } + + pragma [noinline] + predicate call_points_to_builtin_function(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(BuiltinCallable b | + not b = builtin_object("isinstance") and + not b = builtin_object("issubclass") and + not b = builtin_object("callable") and + f = get_a_call(b, context) and + cls = b.getAReturnType() + ) and + f = origin and + if cls = theNoneType() then + value = theNoneObject() + else + value = f + } + + /** Holds if call is to an object that always returns its first argument. + * Typically, this is for known decorators and the like. + * The current implementation only accounts for instances of `zope.interface.declarations.implementer` and + * calls to `functools.wraps(fn)`. + */ + private predicate annotation_call(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + points_to(f.getArg(0), context, value, cls, origin) and + ( + points_to(f.getFunction(), context, _, zopeInterfaceImplementer(), _) + or + points_to(f.getFunction().(CallNode).getFunction(), context, functoolsWraps(), _, _) + ) + } + + private ClassObject zopeInterfaceImplementer() { + result.getName() = "implementer" and + result.getPyClass().getEnclosingModule().getName() = "zope.interface.declarations" + } + + private PyFunctionObject functoolsWraps() { + result.getName() = "wraps" and + result.getFunction().getEnclosingModule().getName() = "functools" + } + + pragma [noinline] + predicate call_to_procedure_points_to(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(PyFunctionObject func | + f = get_a_call(func, context) and + implicitly_returns(func, value, cls) and origin.getNode() = func.getOrigin() + ) + } + + predicate call_to_unknown(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + value = unknownValue() and cls = theUnknownType() and origin = f + and + exists(ControlFlowNode callable | + callable = f.getFunction() or + callable = f.getFunction().(AttrNode).getObject() + | + points_to(callable, context, unknownValue(), _, _) + ) + } + + predicate call_to_type_new(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + points_to(f.getFunction(), context, theTypeNewMethod(), _, _) and + value = theUnknownType() and cls = theUnknownType() and origin = f + } + + predicate call_to_generator_points_to(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(PyFunctionObject func | + f = get_a_call(func, context) | + func.getFunction().isGenerator() and origin = f and value = f and cls = theGeneratorType() + ) + } + + /* Helper for call_points_to_python_function */ + predicate return_val_points_to(PyFunctionObject func, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(ControlFlowNode rval | + rval = func.getAReturnedNode() and + points_to(rval, context, value, cls, origin) + ) + } + + pragma [noinline] + predicate call_points_to_python_function(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(PyFunctionObject func, PointsToContext callee | + return_val_points_to(func, callee, value, cls, origin) and + callee.fromCall(f, func, context) + ) + } + + /** A call, including calls to `type(arg)`, functions and classes. + * + * Call analysis logic + * =================== + * There are five possibilities (that we currently account for) here. + * 1. `type(known_type)` where we know the class of `known_type` and we know its origin + * 2. `type(known_type)` where we know the class of `known_type`, + * but we don't know its origin (because it is a builtin type) + * 3. `Class(...)` where Class is any class except type (with one argument) and calls to that class return instances of that class + * 4. `func(...)` where we know the return type of func (because it is a builtin function) + * 5. `func(...)` where we know the returned object and origin of func (because it is a Python function) + */ + predicate call_points_to(CallNode f, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + /* Case 1 */ + call_to_type_known_python_class_points_to(f, context, value, cls, origin) + or + /* Case 2 */ + call_to_type_known_builtin_class_points_to(f, context, value, cls, origin) + or + /* Case 3 */ + instantiation(f, context, cls) and value = f and f = origin + or + /* Case 4 */ + call_points_to_builtin_function(f, context, value, cls, origin) + or + /* Case 5a */ + call_points_to_python_function(f, context, value, cls, origin) + or + /* Case 5b */ + call_to_generator_points_to(f, context, value, cls, origin) + or + /* Case 5c */ + call_to_procedure_points_to(f, context, value, cls, origin) + or + call_to_unknown(f, context, value, cls, origin) + or + call_to_type_new(f, context, value, cls, origin) + or + annotation_call(f, context, value, cls, origin) + } + + /** INTERNAL -- Public for testing only. + * Whether `call` is a call to `method` of the form `super(...).method(...)` + */ + predicate super_method_call(PointsToContext context, CallNode call, EssaVariable self, FunctionObject method) { + exists(ControlFlowNode func, SuperBoundMethod bound_method | + call.getFunction() = func and + points_to(func, context, bound_method, _, _) and + method = bound_method.getFunction(context) and + self = bound_method.getSelf() + ) + } + + /** INTERNAL -- Use `FunctionObject.getAMethodCall()`. */ + pragma [nomagic] + predicate plain_method_call(FunctionObject func, PointsToContext context, CallNode call) { + exists(ControlFlowNode attr, ClassObject cls, string name | + attr = call.getFunction() and + receiver_type_for(cls, name, attr, context) and + Types::class_attribute_lookup(cls, name, func, _, _) + ) + } + + /** INTERNAL -- Do not use; part of the internal API. + * + * Whether cls `cls` is the receiver type of an attribute access `n`. + * Also bind the name of the attribute. + */ + predicate receiver_type_for(ClassObject cls, string name, ControlFlowNode n, PointsToContext context) { + /* `super().meth()` is not a method on `super` */ + cls != theSuperType() and + exists(Object o | + /* list.__init__() is not a call to type.__init__() */ + not o instanceof ClassObject | + points_to(n.(AttrNode).getObject(name), context, o, cls, _) + ) + or + exists(PlaceHolder p, Variable v | + n.getNode() = p and n.(NameNode).uses(v) and name = v.getId() and + p.getScope().getScope() = cls.getPyClass() and context.appliesTo(n) + ) + } + + /** Gets the argument for the parameter at `position` where `call` is a call to `func`. + * Handles method calls, such that for a call `x.foo()` with `position equal to 0, the result is `x`. + */ + pragma [nomagic] + ControlFlowNode get_argument_for_call_by_position(FunctionObject func, PointsToContext context, CallNode call, int position) { + method_call(func, context, call) and + ( + result = call.getArg(position-1) + or + position = 0 and result = call.getFunction().(AttrNode).getObject() + ) + or + function_call(func, context, call) and + result = call.getArg(position) + } + + /** Holds if `value` is the value attached to the keyword argument `name` in `call`. */ + predicate keyword_value_for_call(CallNode call, string name, ControlFlowNode value) { + exists(Keyword kw | + call.getNode().getAKeyword() = kw | + kw.getArg() = name and kw.getValue() = value.getNode() and + value.getBasicBlock().dominates(call.getBasicBlock()) + ) + } + + /** Gets the value for the keyword argument `name` in `call`, where `call` calls `func` in context. */ + ControlFlowNode get_argument_for_call_by_name(FunctionObject func, PointsToContext context, CallNode call, string name) { + call = get_a_call(func, context) and + keyword_value_for_call(call, name, result) + } + + /** Holds if `func` implicitly returns the `None` object */ + predicate implicitly_returns(PyFunctionObject func, Object none_, ClassObject noneType) { + noneType = theNoneType() and not func.getFunction().isGenerator() and none_ = theNoneObject() and + ( + not exists(func.getAReturnedNode()) and exists(func.getFunction().getANormalExit()) + or + exists(Return ret | ret.getScope() = func.getFunction() and not exists(ret.getValue())) + ) + } + + } + + cached module Flow { + + /** Model the transfer of values at scope-entry points. Transfer from `(pred_var, pred_context)` to `(succ_def, succ_context)`. */ + cached predicate scope_entry_value_transfer(EssaVariable pred_var, PointsToContext pred_context, ScopeEntryDefinition succ_def, PointsToContext succ_context) { + scope_entry_value_transfer_from_earlier(pred_var, pred_context, succ_def, succ_context) + or + callsite_entry_value_transfer(pred_var, pred_context, succ_def, succ_context) + or + pred_context.isImport() and pred_context = succ_context and + class_entry_value_transfer(pred_var, succ_def) + } + + /** Helper for `scope_entry_value_transfer`. Transfer of values from a temporally earlier scope to later scope. + * Earlier and later scopes are, for example, a module and functions in that module, or an __init__ method and another method. */ + pragma [noinline] + private predicate scope_entry_value_transfer_from_earlier(EssaVariable pred_var, PointsToContext pred_context, ScopeEntryDefinition succ_def, PointsToContext succ_context) { + exists(Scope pred_scope, Scope succ_scope | + BaseFlow::scope_entry_value_transfer_from_earlier(pred_var, pred_scope, succ_def, succ_scope) and + succ_context.appliesToScope(succ_scope) + | + succ_context.isRuntime() and succ_context = pred_context + or + pred_context.isImport() and pred_scope instanceof ImportTimeScope and + (succ_context.fromRuntime() or + /* A call made at import time, but from another module. Assume this module has been fully imported. */ + succ_context.isCall() and exists(CallNode call | succ_context.fromCall(call, _) and call.getEnclosingModule() != pred_scope)) + or + /* If predecessor scope is main, then we assume that any global defined exactly once + * is available to all functions. Although not strictly true, this gives less surprising + * results in practice. */ + pred_context.isMain() and pred_scope instanceof Module and succ_context.fromRuntime() and + not strictcount(pred_var.getSourceVariable().(Variable).getAStore()) > 1 + ) + or + exists(NonEscapingGlobalVariable var | + var = pred_var.getSourceVariable() and var = succ_def.getSourceVariable() and + pred_var.getAUse() = succ_context.getRootCall() and pred_context.isImport() and + succ_context.appliesToScope(succ_def.getScope()) + ) + } + + /** Helper for `scope_entry_value_transfer`. + * Transfer of values from the callsite to the callee, for enclosing variables, but not arguments/parameters. */ + pragma [noinline] + private predicate callsite_entry_value_transfer(EssaVariable caller_var, PointsToContext caller_context, ScopeEntryDefinition entry_def, PointsToContext callee_context) { + exists(CallNode callsite, FunctionObject f, Variable var | + scope_entry_function_and_variable(entry_def, f, var) and + callee_context.fromCall(callsite, f, caller_context) and + caller_var.getSourceVariable() = var and + caller_var.getAUse() = callsite + ) + } + + /** Helper for callsite_entry_value_transfer to improve join-order */ + private predicate scope_entry_function_and_variable(ScopeEntryDefinition entry_def, FunctionObject f, Variable var) { + exists(Function func | + func = f.getFunction() | + entry_def.getDefiningNode() = func.getEntryNode() and + not var.getScope() = func and + entry_def.getSourceVariable() = var + ) + } + + /** Helper for `scope_entry_value_transfer`. */ + private predicate class_entry_value_transfer(EssaVariable pred_var, ScopeEntryDefinition succ_def) { + exists(ImportTimeScope scope, ControlFlowNode class_def | + class_def = pred_var.getAUse() and + scope.entryEdge(class_def, succ_def.getDefiningNode()) and + pred_var.getSourceVariable() = succ_def.getSourceVariable() + ) + } + + /** Gets the ESSA variable from which `def` acquires its value, when a call occurs. + * Helper for `callsite_points_to`. */ + cached predicate callsite_exit_value_transfer(EssaVariable callee_var, PointsToContext callee_context, CallsiteRefinement def, PointsToContext callsite_context) { + exists(FunctionObject func, Variable var | + callee_context.fromCall(def.getCall(), func, callsite_context) and + def.getSourceVariable() = var and + var_at_exit(var, func, callee_var) + ) + } + + /* Helper for callsite_exit_value_transfer */ + private predicate var_at_exit(Variable var, FunctionObject func, EssaVariable evar) { + not var instanceof LocalVariable and + evar.getSourceVariable() = var and + evar.getScope() = func.getFunction() and + BaseFlow::reaches_exit(evar) + } + + /** Holds if the `(argument, caller)` pair matches up with `(param, callee)` pair across call. */ + cached predicate callsite_argument_transfer(ControlFlowNode argument, PointsToContext caller, ParameterDefinition param, PointsToContext callee) { + exists(CallNode call, PyFunctionObject func, int n, int offset | + callsite_calls_function(call, caller, func, callee, offset) and + argument = call.getArg(n) and + param = func.getParameter(n+offset) + ) + } + + cached predicate callsite_calls_function(CallNode call, PointsToContext caller, PyFunctionObject func, PointsToContext callee, int parameter_offset) { + /* Functions */ + callee.fromCall(call, func, caller) and + function_call(func, caller, call) and + parameter_offset = 0 + or + /* Methods */ + callee.fromCall(call, func, caller) and + method_call(func, caller, call) and + parameter_offset = 1 + or + /* Classes */ + exists(ClassObject cls | + instantiation(call, caller, cls) and + Types::class_attribute_lookup(cls, "__init__", func, _, _) and + parameter_offset = 1 and + callee.fromCall(call, caller) + ) + } + + /** Helper for `import_star_points_to`. */ + cached predicate module_and_name_for_import_star(ModuleObject mod, string name, ImportStarRefinement def, PointsToContext context) { + points_to(def.getDefiningNode().(ImportStarNode).getModule(), context, mod, _, _) and + name = def.getSourceVariable().getName() + } + + /** Holds if `def` is technically a definition of `var`, but the `from ... import *` does not in fact define `var`. */ + cached predicate variable_not_redefined_by_import_star(EssaVariable var, PointsToContext context, ImportStarRefinement def) { + var = def.getInput() and + exists(ModuleObject mod | + points_to(def.getDefiningNode().(ImportStarNode).getModule(), context, mod, _, _) | + module_exports_boolean(mod, var.getSourceVariable().getName()) = false + or + exists(Module m, string name | + m = mod.getModule() and name = var.getSourceVariable().getName() | + not m.declaredInAll(_) and name.charAt(0) = "_" + ) + ) + } + + } + + private module SSA { + + + /** Holds if the phi-function `phi` refers to `(value, cls, origin)` given the context `context`. */ + pragma [noinline] + private predicate ssa_phi_points_to(PhiFunction phi, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + exists(EssaVariable input, BasicBlock pred | + input = phi.getInput(pred) and + ssa_variable_points_to(input, context, value, cls, origin) + | + Layer::controlledReachableEdge(pred, phi.getBasicBlock(), context) + or + not exists(ConditionBlock guard | guard.controlsEdge(pred, phi.getBasicBlock(), _)) + ) + or + ssa_variable_points_to(phi.getShortCircuitInput(), context, value, cls, origin) + } + + /** Holds if the ESSA definition `def` refers to `(value, cls, origin)` given the context `context`. */ + predicate ssa_definition_points_to(EssaDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + ssa_phi_points_to(def, context, value, cls, origin) + or + ssa_node_definition_points_to(def, context, value, cls, origin) + or + Filters::ssa_filter_definition_points_to(def, context, value, cls, origin) + or + ssa_node_refinement_points_to(def, context, value, cls, origin) + } + + pragma [nomagic] + private predicate ssa_node_definition_points_to_unpruned(EssaNodeDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + assignment_points_to(def, context, value, cls, origin) + or + parameter_points_to(def, context, value, cls, origin) + or + self_parameter_points_to(def, context, value, cls, origin) + or + delete_points_to(def, context, value, cls, origin) + or + scope_entry_points_to(def, context, value, cls, origin) + or + implicit_submodule_points_to(def, context, value, cls, origin) + or + module_name_points_to(def, context, value, cls, origin) + or + iteration_definition_points_to(def, context, value, cls, origin) + /* + * No points-to for non-local function entry definitions yet. + */ + } + + pragma [noinline] + private predicate reachable_definitions(EssaNodeDefinition def) { + Layer::reachableBlock(def.getDefiningNode().getBasicBlock(), _) + } + + pragma [noinline] + private predicate ssa_node_definition_points_to(EssaNodeDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + reachable_definitions(def) and + ssa_node_definition_points_to_unpruned(def, context, value, cls, origin) + } + + pragma [noinline] + private predicate ssa_node_refinement_points_to(EssaNodeRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + method_callsite_points_to(def, context, value, cls, origin) + or + import_star_points_to(def, context, value, cls, origin) + or + attribute_assignment_points_to(def, context, value, cls, origin) + or + callsite_points_to(def, context, value, cls, origin) + or + argument_points_to(def, context, value, cls, origin) + or + attribute_delete_points_to(def, context, value, cls, origin) + or + Filters::uni_edged_phi_points_to(def, context, value, cls, origin) + } + + /** Points-to for normal assignments `def = ...`. */ + pragma [noinline] + private predicate assignment_points_to(AssignmentDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + points_to(def.getValue(), context, value, cls, origin) + } + + /** Helper for `parameter_points_to` */ + pragma [noinline] + private predicate positional_parameter_points_to(ParameterDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(PointsToContext caller, ControlFlowNode arg | + points_to(arg, caller, value, cls, origin) and + Flow::callsite_argument_transfer(arg, caller, def, context) + ) + or + not def.isSelf() and not def.getParameter().isVarargs() and not def.getParameter().isKwargs() and + context.isRuntime() and value = unknownValue() and cls = theUnknownType() and origin = def.getDefiningNode() + } + + /** Helper for `parameter_points_to` */ + pragma [noinline] + private predicate named_parameter_points_to(ParameterDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + exists(CallNode call, PointsToContext caller, FunctionObject func, string name | + context.fromCall(call, func, caller) and + def.getParameter() = func.getFunction().getArgByName(name) and + points_to(call.getArgByName(name), caller, value, cls, origin) + ) + } + + /** Points-to for parameter. `def foo(param): ...`. */ + pragma [noinline] + private predicate parameter_points_to(ParameterDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + positional_parameter_points_to(def, context, value, cls, origin) + or + named_parameter_points_to(def, context, value, cls, origin) + or + default_parameter_points_to(def, context, value, cls, origin) + or + special_parameter_points_to(def, context, value, cls, origin) + } + + /** Helper for parameter_points_to */ + private predicate default_parameter_points_to(ParameterDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + default_value_points_to(def, value, cls, origin) and + context_for_default_value(def, context) + } + + /** Helper for default_parameter_points_to */ + pragma [noinline] + private predicate default_value_points_to(ParameterDefinition def, Object value, ClassObject cls, ControlFlowNode origin) { + exists(PointsToContext imp | imp.isImport() | points_to(def.getDefault(), imp, value, cls, origin)) + } + + /** Helper for default_parameter_points_to */ + pragma [noinline] + private predicate context_for_default_value(ParameterDefinition def, PointsToContext context) { + context.isRuntime() + or + exists(PointsToContext caller, CallNode call, FunctionObject func, int n | + context.fromCall(call, func, caller) and + func.getFunction().getArg(n) = def.getParameter() and + not exists(call.getArg(n)) and + not exists(call.getArgByName(def.getParameter().asName().getId())) and + not exists(call.getNode().getKwargs()) and + not exists(call.getNode().getStarargs()) + ) + } + + /** Helper for parameter_points_to */ + pragma [noinline] + private predicate special_parameter_points_to(ParameterDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + context.isRuntime() and + exists(ControlFlowNode param | + param = def.getDefiningNode() | + varargs_points_to(param, cls) and value = theEmptyTupleObject() and origin = param + or + varargs_points_to(param, cls) and value = param and origin = param + or + kwargs_points_to(param, cls) and value = param and origin = param + ) + or + exists(PointsToContext caller, CallNode call, FunctionObject func, Parameter p | + context.fromCall(call, func, caller) and + func.getFunction().getAnArg() = p and p = def.getParameter() and + not p.isSelf() and + not exists(call.getArg(p.getPosition())) and + not exists(call.getArgByName(p.getName())) and + (exists(call.getNode().getKwargs()) or exists(call.getNode().getStarargs())) and + value = unknownValue() and cls = theUnknownType() and origin = def.getDefiningNode() + ) + } + + /** Holds if the `(obj, caller)` pair matches up with `(self, callee)` pair across call. */ + pragma [noinline] + private predicate callsite_self_argument_transfer(EssaVariable obj, PointsToContext caller, ParameterDefinition self, PointsToContext callee) { + self.isSelf() and + exists(CallNode call, PyFunctionObject meth | + meth.getParameter(0) = self and + callee.fromCall(call, caller) | + Calls::plain_method_call(meth, caller, call) and + obj.getASourceUse() = call.getFunction().(AttrNode).getObject() + or + Calls::super_method_call(caller, call, obj, meth) + ) + } + + /** Points-to for self parameter: `def meth(self, ...): ...`. */ + pragma [noinline] + private predicate self_parameter_points_to(ParameterDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + def.isSelf() and + exists(FunctionObject meth, Function scope | + meth.getFunction() = scope | + def.getDefiningNode().getScope() = scope and + context.isRuntime() and context.appliesToScope(scope) and + scope.getScope() = cls.getPyClass() and + Types::concrete_class(cls) and + value = def.getDefiningNode() and origin = value and + /* We want to allow decorated functions, otherwise we lose a lot of useful information. + * However, we want to exclude any function whose arguments are permuted by the decorator. + * In general we can't do that, but we can special case the most common ones. + */ + neither_class_nor_static_method(scope) + ) + or + exists(EssaVariable obj, PointsToContext caller | + ssa_variable_points_to(obj, caller, value, cls, origin) and + callsite_self_argument_transfer(obj, caller, def, context) + ) + or + cls_parameter_points_to(def, context, value, cls, origin) + } + + private predicate neither_class_nor_static_method(Function f) { + not exists(f.getADecorator()) + or + exists(ControlFlowNode deco | + deco = f.getADecorator().getAFlowNode() | + exists(Object o | + points_to(deco, _, o, _, _) | + not o = theStaticMethodType() and + not o = theClassMethodType() + ) + or not deco instanceof NameNode + ) + } + + + pragma [noinline] + private predicate cls_parameter_points_to(ParameterDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + def.isSelf() and + exists(CallNode call, PyFunctionObject meth, Object obj, ClassObject objcls, PointsToContext caller | + context.fromCall(call, caller) and + cls_method_object_points_to(call, caller, meth, obj, objcls, origin) and + def.getScope() = meth.getFunction() + | + obj instanceof ClassObject and value = obj and cls = objcls + or + not obj instanceof ClassObject and value = objcls and cls = Types::class_get_meta_class(objcls) + ) + } + + /* Factor out part of `cls_parameter_points_to` to prevent bad join-order */ + pragma [noinline] + private predicate cls_method_object_points_to(CallNode call, PointsToContext context, PyFunctionObject meth, Object value, ClassObject cls, ControlFlowNode origin) { + exists(AttrNode attr | + class_method_call(_, attr, meth, context, call) and + points_to(attr.getObject(), context, value, cls, origin) + ) + } + + /** Points-to for deletion: `del name`. */ + pragma [noinline] + private predicate delete_points_to(DeletionDefinition def, PointsToContext context, Object value, ClassObject cls, ControlFlowNode origin) { + value = undefinedVariable() and cls = theUnknownType() and origin = def.getDefiningNode() and context.appliesToScope(def.getScope()) + } + + /** Implicit "definition" of the names of submodules at the start of an `__init__.py` file. + * + * PointsTo isn't exactly how the interpreter works, but is the best approximation we can manage statically. + */ + pragma [noinline] + private predicate implicit_submodule_points_to(ImplicitSubModuleDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + exists(PackageObject package | + package.getInitModule().getModule() = def.getDefiningNode().getScope() | + value = package.submodule(def.getSourceVariable().getName()) and + cls = theModuleType() and + origin = value and + context.isImport() + ) + } + + /** Implicit "definition" of `__name__` at the start of a module. */ + pragma [noinline] + private predicate module_name_points_to(ScopeEntryDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + def.getVariable().getName() = "__name__" and + exists(Module m | + m = def.getScope() + | + value = module_dunder_name(m) and context.isImport() + or + value = object_for_string("__main__") and context.isMain() and context.appliesToScope(m) + ) and + cls = theStrType() and origin = def.getDefiningNode() + } + + private Object module_dunder_name(Module m) { + exists(string name | + result = object_for_string(name) | + if m.isPackageInit() then + name = m.getPackage().getName() + else + name = m.getName() + ) + } + + /** Definition of iteration variable in loop */ + pragma [noinline] + private predicate iteration_definition_points_to(IterationDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + points_to(def.getSequence(), context, unknownValue(), _, _) and + value = unknownValue() and cls = theUnknownType() and origin = def.getDefiningNode() + } + + /** Points-to for implicit variable declarations at scope-entry. */ + pragma [noinline] + private predicate scope_entry_points_to(ScopeEntryDefinition def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + /* Transfer from another scope */ + exists(EssaVariable var, PointsToContext outer | + Flow::scope_entry_value_transfer(var, outer, def, context) and + ssa_variable_points_to(var, outer, value, cls, origin) + ) + or + /* Undefined variable */ + exists(Scope scope | + not def.getVariable().getName() = "__name__" and + not def.getVariable().getName() = "*" and + def.getScope() = scope and context.appliesToScope(scope) | + def.getSourceVariable() instanceof GlobalVariable and scope instanceof Module + or + def.getSourceVariable() instanceof LocalVariable and (context.isImport() or context.isRuntime() or context.isMain()) + ) and + value = undefinedVariable() and cls = theUnknownType() and origin = def.getDefiningNode() + or + /* Builtin not defined in outer scope */ + exists(Module mod, GlobalVariable var | + var = def.getSourceVariable() and + mod = def.getScope().getEnclosingModule() and + context.appliesToScope(def.getScope()) and + not exists(EssaVariable v | v.getSourceVariable() = var and v.getScope() = mod) and + builtin_name_points_to(var.getId(), value, cls) and origin = value + ) + } + + /** Points-to for a variable (possibly) redefined by a call: + * `var = ...; foo(); use(var)` + * Where var may be redefined in call to `foo` if `var` escapes (is global or non-local). + */ + pragma [noinline] + private predicate callsite_points_to(CallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + exists(EssaVariable var, PointsToContext callee | + Flow::callsite_exit_value_transfer(var, callee, def, context) and + ssa_variable_points_to(var, callee, value, cls, origin) + ) + or + callsite_points_to_python(def, context, value, cls, origin) + or + callsite_points_to_builtin(def, context, value, cls, origin) + } + + pragma [noinline] + private predicate callsite_points_to_python(CallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + ssa_variable_points_to(def.getInput(), context, value, cls, origin) and + exists(CallNode call, PythonSsaSourceVariable var | + call = def.getCall() and + var = def.getSourceVariable() and + context.untrackableCall(call) and + exists(PyFunctionObject modifier | + call = get_a_call(modifier, context) and + not modifies_escaping_variable(modifier, var) + ) + ) + } + + private predicate modifies_escaping_variable(FunctionObject modifier, PythonSsaSourceVariable var) { + exists(var.redefinedAtCallSite()) and + modifier.getFunction().getBody().contains(var.(Variable).getAStore()) + } + + pragma [noinline] + private predicate callsite_points_to_builtin(CallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + ssa_variable_points_to(def.getInput(), context, value, cls, origin) and + exists(CallNode call | + call = def.getCall() | + // An identifiable callee is a builtin + exists(BuiltinCallable opaque | get_a_call(opaque, _) = call) + ) + } + + /** Pass through for `self` for the implicit re-definition of `self` in `self.foo()`. */ + private predicate method_callsite_points_to(MethodCallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + /* The value of self remains the same, only the attributes may change */ + ssa_variable_points_to(def.getInput(), context, value, cls, origin) + } + + /** Points-to for `from ... import *`. */ + private predicate import_star_points_to(ImportStarRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + exists(ModuleObject mod, string name | + Flow::module_and_name_for_import_star(mod, name, def, context) | + /* Attribute from imported module */ + module_exports(mod, name) and + Layer::module_attribute_points_to(mod, name, value, cls, origin) + ) + or + exists(EssaVariable var | + /* Retain value held before import */ + Flow::variable_not_redefined_by_import_star(var, context, def) and + ssa_variable_points_to(var, context, value, cls, origin) + ) + } + + /** Attribute assignments have no effect as far as value tracking is concerned, except for `__class__`. */ + pragma [noinline] + private predicate attribute_assignment_points_to(AttributeAssignment def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + if def.getName() = "__class__" then + ssa_variable_points_to(def.getInput(), context, value, _, _) and points_to(def.getValue(), _, cls, _,_) and + origin = def.getDefiningNode() + else + ssa_variable_points_to(def.getInput(), context, value, cls, origin) + } + + /** Ignore the effects of calls on their arguments. PointsTo is an approximation, but attempting to improve accuracy would be very expensive for very little gain. */ + pragma [noinline] + private predicate argument_points_to(ArgumentRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + ssa_variable_points_to(def.getInput(), context, value, cls, origin) + } + + /** Attribute deletions have no effect as far as value tracking is concerned. */ + pragma [noinline] + private predicate attribute_delete_points_to(EssaAttributeDeletion def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + ssa_variable_points_to(def.getInput(), context, value, cls, origin) + } + + /* Data flow for attributes. These mirror the "normal" points-to predicates. + * For each points-to predicate `xxx_points_to(XXX def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin)` + * There is an equivalent predicate that tracks the values in attributes: + * `xxx_named_attribute_points_to(XXX def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin)` + * */ + + /** INTERNAL -- Public for testing only. + * + * Hold if the attribute `name` of the ssa variable `var` refers to `(value, cls, origin)`. + */ + predicate ssa_variable_named_attribute_points_to(EssaVariable var, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + ssa_definition_named_attribute_points_to(var.getDefinition(), context, name, value, cls, origin) + } + + /** Helper for `ssa_variable_named_attribute_points_to`. */ + private predicate ssa_definition_named_attribute_points_to(EssaDefinition def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + ssa_phi_named_attribute_points_to(def, context, name, value, cls, origin) + or + ssa_node_definition_named_attribute_points_to(def, context, name, value, cls, origin) + or + ssa_node_refinement_named_attribute_points_to(def, context, name, value, cls, origin) + or + Filters::ssa_filter_definition_named_attribute_points_to(def, context, name, value, cls, origin) + } + + /** Holds if the attribute `name` of the ssa phi-function definition `phi` refers to `(value, cls, origin)`. */ + pragma[noinline] + private predicate ssa_phi_named_attribute_points_to(PhiFunction phi, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + ssa_variable_named_attribute_points_to(phi.getAnInput(), context, name, value, cls, origin) + } + + /** Helper for `ssa_definition_named_attribute_points_to`. */ + pragma[noinline] + private predicate ssa_node_definition_named_attribute_points_to(EssaNodeDefinition def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + assignment_named_attribute_points_to(def, context, name, value, cls, origin) + or + delete_named_attribute_points_to(def, context, name, value, cls, origin) + or + self_parameter_named_attribute_points_to(def, context, name, value, cls, origin) + or + scope_entry_named_attribute_points_to(def, context, name, value, cls, origin) + } + + /** Helper for `ssa_definition_named_attribute_points_to`. */ + pragma[noinline] + private predicate ssa_node_refinement_named_attribute_points_to(EssaNodeRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + attribute_assignment_named_attribute_points_to(def, context, name, value, cls, origin) + or + attribute_delete_named_attribute_points_to(def, context, name, value, cls, origin) + or + import_star_named_attribute_points_to(def, context, name, value, cls, origin) + or + self_callsite_named_attribute_points_to(def, context, name, value, cls, origin) + or + argument_named_attribute_points_to(def, context, name, value, cls, origin) + } + + pragma[noinline] + private predicate scope_entry_named_attribute_points_to(ScopeEntryDefinition def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + exists(EssaVariable var, PointsToContext outer | + Flow::scope_entry_value_transfer(var, outer, def, context) and + ssa_variable_named_attribute_points_to(var, outer, name, value, cls, origin) + ) + or + origin = def.getDefiningNode() and + def.getSourceVariable().getName() = "*" and + context.isImport() and + exists(PackageObject package | + package.getInitModule().getModule() = def.getScope() | + explicitly_imported(package.submodule(name)) and + value = undefinedVariable() and + cls = theUnknownType() + ) + } + + pragma[noinline] + private predicate assignment_named_attribute_points_to(AssignmentDefinition def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + named_attribute_points_to(def.getValue(), context, name, value, cls, origin) + } + + pragma[noinline] + private predicate attribute_assignment_named_attribute_points_to(AttributeAssignment def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + points_to(def.getValue(), context, value, cls, origin) and name = def.getName() + or + ssa_variable_named_attribute_points_to(def.getInput(), context, name, value, cls, origin) and not name = def.getName() + } + + /** Holds if `def` defines the attribute `name`. + * + * `def` takes the form `setattr(use, "name")` where `use` is the input to the definition. + */ + private boolean sets_attribute(ArgumentRefinement def, string name) { + exists(ControlFlowNode func, Object obj | + two_args_first_arg_string(def, func, name) and + points_to(func, _, obj, _, _) | + obj = builtin_object("setattr") and result = true + or + obj != builtin_object("setattr") and result = false + ) + } + + private predicate two_args_first_arg_string(ArgumentRefinement def, ControlFlowNode func, string name) { + exists(CallNode call | + call = def.getDefiningNode() and + call.getFunction() = func and + def.getInput().getAUse() = call.getArg(0) and + call.getArg(1).getNode().(StrConst).getText() = name + ) + } + + pragma[noinline] + private predicate argument_named_attribute_points_to(ArgumentRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ObjectOrCfg origin) { + not two_args_first_arg_string(def, _, name) and ssa_variable_named_attribute_points_to(def.getInput(), context, name, value, cls, origin) + or + sets_attribute(def, name) = true and points_to(def.getDefiningNode().(CallNode).getArg(2), context, value, cls, origin) + or + sets_attribute(def, name) = false and ssa_variable_named_attribute_points_to(def.getInput(), context, name, value, cls, origin) + } + + /** Holds if the self variable in the callee (`(var, callee)`) refers to the same object as `def` immediately after the call, (`(def, caller)`). */ + pragma[noinline] + private predicate callee_self_variable(EssaVariable var, PointsToContext callee, SelfCallsiteRefinement def, PointsToContext caller) { + exists(FunctionObject func, LocalVariable self | + callee.fromCall(def.getCall(), func, caller) and + BaseFlow::reaches_exit(var) and + var.getSourceVariable() = self and + self.isSelf() and + self.getScope() = func.getFunction() + ) + } + + pragma[noinline] + private predicate self_callsite_named_attribute_points_to(SelfCallsiteRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ObjectOrCfg origin) { + exists(EssaVariable var, PointsToContext callee | + ssa_variable_named_attribute_points_to(var, callee, name, value, cls, origin) and + callee_self_variable(var, callee, def, context) + ) + } + + /** Gets the (temporally) preceding variable for `self`, e.g. `def` is in method `foo()` and `result` is in `__init__()`. */ + private EssaVariable preceding_self_variable(ParameterDefinition def) { + def.isSelf() and + exists(Function preceding, Function method | + method = def.getScope() and + // Only methods + preceding.isMethod() and preceding.precedes(method) and + BaseFlow::reaches_exit(result) and result.getSourceVariable().(Variable).isSelf() and + result.getScope() = preceding + ) + } + + pragma [noinline] + private predicate self_parameter_named_attribute_points_to(ParameterDefinition def, PointsToContext context, string name, Object value, ClassObject vcls, ControlFlowNode origin) { + context.isRuntime() and executes_in_runtime_context(def.getScope()) and + ssa_variable_named_attribute_points_to(preceding_self_variable(def), context, name, value, vcls, origin) + or + exists(FunctionObject meth, CallNode call, PointsToContext caller_context, ControlFlowNode obj | + meth.getFunction() = def.getScope() and + method_call(meth, caller_context, call) and + call.getFunction().(AttrNode).getObject() = obj and + context.fromCall(call, meth, caller_context) and + named_attribute_points_to(obj, caller_context, name, value, vcls, origin) + ) + } + + private predicate delete_named_attribute_points_to(DeletionDefinition def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + none() + } + + private predicate attribute_delete_named_attribute_points_to(EssaAttributeDeletion def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + none() + } + + /* Helper for import_star_named_attribute_points_to */ + pragma [noinline] + private predicate star_variable_import_star_module(ImportStarRefinement def, ImportStarNode imp, PointsToContext context, ModuleObject mod) { + def.getSourceVariable().getName() = "*" and + exists(ControlFlowNode fmod | + fmod = imp.getModule() and + imp = def.getDefiningNode() and + points_to(fmod, context, mod, _, _) + ) + } + + /* Helper for import_star_named_attribute_points_to */ + pragma [noinline, nomagic] + private predicate ssa_star_variable_input_points_to(ImportStarRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + exists(EssaVariable var | + ssa_star_import_star_input(def, var) and + ssa_variable_named_attribute_points_to(var, context, name, value, cls, origin) + ) + } + + /* Helper for ssa_star_variable_input_points_to */ + pragma [noinline] + private predicate ssa_star_import_star_input(ImportStarRefinement def, EssaVariable var) { + def.getSourceVariable().getName() = "*" and var = def.getInput() + } + + pragma [noinline] + private predicate import_star_named_attribute_points_to(ImportStarRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ControlFlowNode origin) { + exists(ImportStarNode imp, ModuleObject mod | + star_variable_import_star_module(def, imp, context, mod) | + /* Attribute from imported module */ + module_exports_boolean(mod, name) = true and + exists(ObjectOrCfg obj | + Layer::module_attribute_points_to(mod, name, value, cls, obj) and + not exists(Variable v | v.getId() = name and v.getScope() = imp.getScope()) and + origin = origin_from_object_or_here(obj, imp) + ) + or + /* Retain value held before import */ + module_exports_boolean(mod, name) = false and + ssa_star_variable_input_points_to(def, context, name, value, cls, origin) + ) + } + + } + + private module Filters { + + /** Holds if `expr` is the operand of a unary `not` expression. */ + private ControlFlowNode not_operand(ControlFlowNode expr) { + expr.(UnaryExprNode).getNode().getOp() instanceof Not and + result = expr.(UnaryExprNode).getOperand() + } + + /** Gets the value that `expr` evaluates to (when converted to a boolean) when `use` refers to `(val, cls, origin)` + * and `expr` contains `use` and both are contained within a test. */ + pragma [nomagic] + boolean evaluates_boolean(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, Object val, ClassObject cls, ControlFlowNode origin) { + result = isinstance_test_evaluates_boolean(expr, use, context, val, cls, origin) + or + result = issubclass_test_evaluates_boolean(expr, use, context, val, cls, origin) + or + result = equality_test_evaluates_boolean(expr, use, context, val, cls, origin) + or + result = callable_test_evaluates_boolean(expr, use, context, val, cls, origin) + or + result = hasattr_test_evaluates_boolean(expr, use, context, val, cls, origin) + or + result = evaluates(not_operand(expr), use, context, val, cls, origin).booleanNot() + } + + /** Gets the value that `expr` evaluates to (when converted to a boolean) when `use` refers to `(val, cls, origin)` + * and `expr` contains `use` and both are contained within a test. */ + pragma [nomagic] + boolean evaluates(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, Object val, ClassObject cls, ControlFlowNode origin) { + result = evaluates_boolean(expr, use, context, val, cls, origin) + or + result = true and evaluates_int(expr, use, context, val, cls, origin) != 0 + or + result = false and evaluates_int(expr, use, context, val, cls, origin) = 0 + or + result = truth_test_evaluates_boolean(expr, use, context, val, cls, origin) + } + + private boolean maybe() { + result = true or result = false + } + + pragma [nomagic] + private boolean issubclass_test_evaluates_boolean(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, Object val, ClassObject cls, ControlFlowNode origin) { + points_to(use, context, val, cls, origin) and + exists(ControlFlowNode clsNode | + BaseFilters::issubclass(expr, clsNode, use) | + exists(ClassObject scls | + points_to(clsNode, context, scls, _, _) | + result = Types::is_improper_subclass_bool(val, scls) + ) + or exists(TupleObject t, ClassObject scls | + points_to(clsNode, context, t, _, _) and + result = Types::is_improper_subclass_bool(val, scls) and result = true + | + scls = t.getBuiltinElement(_) + or + points_to(t.getSourceElement(_), _, scls, _, _) + ) + or + val = unknownValue() and result = maybe() + or + val = theUnknownType() and result = maybe() + ) + } + + pragma [nomagic] + private boolean isinstance_test_evaluates_boolean(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, Object val, ClassObject cls, ControlFlowNode origin) { + points_to(use, context, val, cls, origin) and + exists(ControlFlowNode clsNode | + BaseFilters::isinstance(expr, clsNode, use) | + exists(ClassObject scls | + points_to(clsNode, context, scls, _, _) | + result = Types::is_improper_subclass_bool(cls, scls) + ) + or exists(TupleObject t, ClassObject scls | + points_to(clsNode, context, t, _, _) and + result = Types::is_improper_subclass_bool(cls, scls) and result = true + | + scls = t.getBuiltinElement(_) + or + points_to(t.getSourceElement(_), _, scls, _, _) + ) + or + val = unknownValue() and result = maybe() + ) + } + + pragma [noinline] + private boolean equality_test_evaluates_boolean(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, Object val, ClassObject cls, ControlFlowNode origin) { + exists(ControlFlowNode l, ControlFlowNode r, boolean sense | + contains_interesting_expression_within_test(expr, use) and + BaseFilters::equality_test(expr, l, sense, r) | + exists(int il, int ir | + il = evaluates_int(l, use, context, val, cls, origin) and ir = simple_int_value(r) + | + result = sense and il = ir + or + result = sense.booleanNot() and il != ir + ) + or + use = l and + exists(Object other | + /* Must be discrete values, not just types of things */ + equatable_value(val) and equatable_value(other) and + points_to(use, context, val, cls, origin) and + points_to(r, context, other, _, _) | + other != val and result = sense.booleanNot() + or + other = val and result = sense + ) + ) + } + + pragma [noinline] + private boolean truth_test_evaluates_boolean(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, Object val, ClassObject cls, ControlFlowNode origin) { + contains_interesting_expression_within_test(expr, use) and + points_to(use, context, val, cls, origin) and + ( + expr = use and val.booleanValue() = result + or + expr = use and Types::instances_always_true(cls) and result = true + ) + } + + pragma [noinline] + private boolean callable_test_evaluates_boolean(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, Object val, ClassObject cls, ControlFlowNode origin) { + contains_interesting_expression_within_test(expr, use) and + points_to(use, context, val, cls, origin) and + BaseFilters::is_callable(expr, use) and + ( + result = Types::class_has_attribute_bool(cls, "__call__") + or + cls = theUnknownType() and result = maybe() + ) + } + + pragma [noinline] + private boolean hasattr_test_evaluates_boolean(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, Object val, ClassObject cls, ControlFlowNode origin) { + contains_interesting_expression_within_test(expr, use) and + points_to(use, context, val, cls, origin) and + exists(string name | + BaseFilters::hasattr(expr, use, name) | + result = Types::class_has_attribute_bool(cls, name) + ) + } + + /** Holds if meaningful equality tests can be made with `o`. + * True for basic objects like 3 or None, but it is also true for sentinel objects. + */ + predicate equatable_value(Object o) { + comparable_value(o) + or + o.(ControlFlowNode).getScope() instanceof Module and + exists(ClassObject c | + c.isBuiltin() and + points_to(o.(CallNode).getFunction(), _, c, _, _) + ) + } + + /** Holds if meaningful comparisons can be made with `o`. + * True for basic objects like 3 or None. + */ + predicate comparable_value(Object o) { + o.isBuiltin() and not o = unknownValue() and not o = undefinedVariable() + or + exists(o.booleanValue()) + } + + + /** Holds if the test on `use` is a test that we can potentially understand */ + private predicate comprehensible_test(ControlFlowNode test, ControlFlowNode use) { + BaseFilters::issubclass(test, _, use) + or + BaseFilters::isinstance(test, _, use) + or + BaseFilters::equality_test(test, use, _, _) + or + exists(ControlFlowNode l | + BaseFilters::equality_test(test, l, _, _) | + literal_or_len(l) + ) + or + BaseFilters::is_callable(test, use) + or + BaseFilters::hasattr(test, use, _) + or + test = use + or + literal_or_len(test) + or + comprehensible_test(not_operand(test), use) + } + + + /** Gets the simple integer value of `f` for numeric literals. */ + private int simple_int_value(ControlFlowNode f) { + exists(NumericObject num | + points_to(f, _, num, _, _) and + result = num.intValue() + ) + } + + /** Gets the integer value that `expr` evaluates to given that `use` refers to `val` and `use` is a part of `expr`. + * Only applies to numeric literal and `len()` of sequences. */ + pragma [noinline] + private int evaluates_int(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, Object val, ClassObject cls, ControlFlowNode origin) { + contains_interesting_expression_within_test(expr, use) and + points_to(use, context, val, cls, origin) and + ( + exists(CallNode call | + call = expr and + points_to(call.getFunction(), context, theLenFunction(), _, _) and + use = call.getArg(0) and + val.(SequenceObject).getLength() = result + ) + or + expr = use and result = val.(NumericObject).intValue() + ) + } + + private predicate literal_or_len(ControlFlowNode expr) { + expr.getNode() instanceof Num + or + expr.(CallNode).getFunction().(NameNode).getId() = "len" + } + + /** Holds if ESSA edge refinement, `def`, refers to `(value, cls, origin)`. */ + predicate ssa_filter_definition_points_to(PyEdgeRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + exists(ControlFlowNode test, ControlFlowNode use | + refinement_test(test, use, test_evaluates_boolean(test, use, context, value, cls, origin), def) + ) + or + /* If we can't understand the test, assume that value passes through. + * Or, if the value is `unknownValue()` then let it pass through as well. */ + exists(ControlFlowNode test, ControlFlowNode use | + refinement_test(test, use, _, def) and + ssa_variable_points_to(def.getInput(), context, value, cls, origin) | + not comprehensible_test(test, use) or + value = unknownValue() + ) + } + + /** Holds if ESSA definition, `uniphi`, refers to `(value, cls, origin)`. */ + pragma [noinline] + predicate uni_edged_phi_points_to(SingleSuccessorGuard uniphi, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { + exists(ControlFlowNode test, ControlFlowNode use | + /* Because calls such as `len` may create a new variable, we need to go via the source variable + * That is perfectly safe as we are only dealing with calls that do not mutate their arguments. + */ + use = uniphi.getInput().getSourceVariable().(Variable).getAUse() and + test = uniphi.getDefiningNode() and + uniphi.getSense() = test_evaluates_boolean(test, use, context, value, cls, origin) + ) + } + + /** Holds if the named attibute of ESSA edge refinement, `def`, refers to `(value, cls, origin)`. */ + pragma[noinline] + predicate ssa_filter_definition_named_attribute_points_to(PyEdgeRefinement def, PointsToContext context, string name, Object value, ClassObject cls, ObjectOrCfg origin) { + exists(ControlFlowNode test, AttrNode use, boolean sense | + edge_refinement_attr_use_sense(def, test, use, name, sense) and + sense = test_evaluates_boolean(test, use, context, value, cls, origin) + ) + or + exists(EssaVariable input | + input = def.getInput() and + not edge_refinement_test(def, input, name) and + SSA::ssa_variable_named_attribute_points_to(input, context, name, value, cls, origin) + ) + } + + /* Helper for ssa_filter_definition_named_attribute_points_to + * Holds if `use` is of the form `var.name` in the test of `def`, and `var` is the source variable of `def`, and `def` has sense `sense`. + */ + pragma[noinline] + private predicate edge_refinement_attr_use_sense(PyEdgeRefinement def, ControlFlowNode test, AttrNode use, string name, boolean sense) { + def.getSense() = sense and + exists(EssaVariable input | + input = def.getInput() and + test = def.getTest() and + use.getObject(name) = def.getInput().getSourceVariable().(Variable).getAUse() and + test_contains(test, use) + ) + } + + /* Helper for ssa_filter_definition_named_attribute_points_to */ + pragma[noinline] + private predicate edge_refinement_test(PyEdgeRefinement def, EssaVariable input, string name) { + exists(ControlFlowNode test | + input = def.getInput() and + test = def.getTest() | + exists(AttrNode use | + refinement_test(test, use.getObject(name), _, def) + ) + ) + } + + } + + cached module Types { + + /** INTERNAL -- Use `ClassObject.getBaseType(n)` instead. + * + * Gets the nth base class of the class. */ + cached Object class_base_type(ClassObject cls, int n) { + not result = unknownValue() and + exists(ClassExpr cls_expr | cls.getOrigin() = cls_expr | + points_to(cls_expr.getBase(n).getAFlowNode(), _, result, _, _) + or + is_new_style(cls) and not exists(cls_expr.getBase(0)) and result = theObjectType() and n = 0 + ) + or + result = builtin_base_type(cls) and n = 0 + or + cls = theUnknownType() and result = theObjectType() and n = 0 + } + + private Object py_base_type(ClassObject cls, int n) { + not result = unknownValue() and + exists(ClassExpr cls_expr | cls.getOrigin() = cls_expr | + points_to(cls_expr.getBase(n).getAFlowNode(), _, result, _, _) + ) + } + + cached int class_base_count(ClassObject cls) { + exists(ClassExpr cls_expr | + cls.getOrigin() = cls_expr | + result = strictcount(cls_expr.getABase()) + or + is_new_style_bool(cls) = true and not exists(cls_expr.getBase(0)) and result = 1 + or + is_new_style_bool(cls) = false and not exists(cls_expr.getBase(0)) and result = 0 + ) + or + cls = theObjectType() and result = 0 + or + exists(builtin_base_type(cls)) and not cls = theObjectType() and result = 1 + or + cls = theUnknownType() and result = 1 + } + + /** INTERNAL -- Do not not use. + * + * Holds if a call to this class will return an instance of this class. + */ + cached predicate callToClassWillReturnInstance(ClassObject cls) { + callToClassWillReturnInstance(cls, 0) and + not callToPythonClassMayNotReturnInstance(cls.getPyClass()) + } + + private predicate callToClassWillReturnInstance(ClassObject cls, int n) { + n = class_base_count(cls) + or + callToClassWillReturnInstance(cls, n+1) and + exists(ClassObject base | + base = class_base_type(cls, n) | + /* Most builtin types "declare" `__new__`, such as `int`, yet are well behaved. */ + base.isBuiltin() + or + exists(Class c | + c = cls.getPyClass() and + not callToPythonClassMayNotReturnInstance(c) + ) + ) + } + + private predicate callToPythonClassMayNotReturnInstance(Class cls) { + /* Django does this, so we need to account for it */ + exists(Function init, LocalVariable self | + /* `self.__class__ = ...` in the `__init__` method */ + cls.getInitMethod() = init and + self.isSelf() and self.getScope() = init and + exists(AttrNode a | a.isStore() and a.getObject("__class__") = self.getAUse()) + ) + or + exists(Function new | new.getName() = "__new__" and new.getScope() = cls) + } + + cached boolean is_new_style_bool(ClassObject cls) { + major_version() = 3 and result = true + or + cls.isBuiltin() and result = true + or + get_an_improper_super_type(class_get_meta_class(cls)) = theTypeType() and result = true + or + class_get_meta_class(cls) = theClassType() and result = false + } + + /** INTERNAL -- Use `ClassObject.isNewStyle()` instead. */ + cached predicate is_new_style(ClassObject cls) { + is_new_style_bool(cls) = true + or + is_new_style(get_a_super_type(cls)) + } + + /** INTERNAL -- Use `ClassObject.getASuperType()` instead. */ + cached ClassObject get_a_super_type(ClassObject cls) { + result = class_base_type(cls, _) + or + result = class_base_type(get_a_super_type(cls), _) + } + + /** INTERNAL -- Use `ClassObject.getAnImproperSuperType()` instead. */ + cached ClassObject get_an_improper_super_type(ClassObject cls) { + result = cls + or + result = get_a_super_type(cls) + } + + cached boolean is_subclass_bool(ClassObject cls, ClassObject sup) { + if abcSubclass(cls, sup) then ( + /* Hard-code some abc subclass pairs -- In future we may change this to use stubs. */ + result = true + ) else ( + sup = class_base_type(cls, _) and result = true + or + is_subclass_bool(class_base_type(cls, _), sup) = true and result = true + or + result = is_subclass_bool(cls, sup, 0) + ) + } + + private predicate abcSubclass(ClassObject cls, ClassObject sup) { + cls = theListType() and sup = collectionsAbcClass("Iterable") + or + cls = theSetType() and sup = collectionsAbcClass("Iterable") + or + cls = theDictType() and sup = collectionsAbcClass("Iterable") + or + cls = theSetType() and sup = collectionsAbcClass("Set") + or + cls = theListType() and sup = collectionsAbcClass("Sequence") + or + cls = theDictType() and sup = collectionsAbcClass("Mapping") + } + + cached boolean is_improper_subclass_bool(ClassObject cls, ClassObject sup) { + result = is_subclass_bool(cls, sup) + or + result = true and cls = sup + } + + private boolean is_subclass_bool(ClassObject cls, ClassObject sup, int n) { + relevant_subclass_relation(cls, sup) and + ( + n = class_base_count(cls) and result = false and not cls = sup + or + exists(ClassObject basetype | + basetype = class_base_type(cls, n) | + not basetype = sup and + result = is_subclass_bool(cls, sup, n+1).booleanOr(is_subclass_bool(basetype, sup)) + or + basetype = sup and result = true + ) + ) + } + + private predicate relevant_subclass_relation(ClassObject cls, ClassObject sup) { + exists(ControlFlowNode supnode, ControlFlowNode use | + points_to(supnode, _, sup, _, _) + or exists(TupleObject t | + points_to(supnode, _, t, _, _) | + sup = t.getBuiltinElement(_) + or + points_to(t.getSourceElement(_), _, sup, _, _) + ) + | + BaseFilters::issubclass(_, supnode, use) and points_to(use, _, cls, _, _) + or + BaseFilters::isinstance(_, supnode, use) and points_to(use, _, _, cls, _) + ) + or + exists(ClassObject sub | + relevant_subclass_relation(sub, sup) and + class_base_type(sub, _) = cls + ) + } + + cached ClassList get_mro(ClassObject cls) { + result = new_style_mro(cls) and is_new_style_bool(cls) = true + or + result = old_style_mro(cls) and is_new_style_bool(cls) = false + } + + /** INTERNAL -- Use `ClassObject.declaredAttribute(name). instead. */ + cached predicate class_declared_attribute(ClassObject owner, string name, Object value, ClassObject vcls, ObjectOrCfg origin) { + /* Note that src_var must be a local variable, we aren't interested in the value that any global variable may hold */ + not value = undefinedVariable() and + exists(EssaVariable var, LocalVariable src_var | + var.getSourceVariable() = src_var and + src_var.getId() = name and + var.getAUse() = owner.getImportTimeScope().getANormalExit() | + ssa_variable_points_to(var, _, value, vcls, origin) + ) + or + value = builtin_class_attribute(owner, name) and class_declares_attribute(owner, name) and + origin = value and vcls = builtin_object_type(value) + } + + private predicate interesting_class_attribute(ClassList mro, string name) { + exists(ControlFlowNode use, ClassObject cls | + mro = cls.getMro() and + BaseFilters::hasattr(_, use, name) | + points_to(use, _, cls, _, _) or + points_to(use, _, _, cls, _) + ) + or + exists(ClassList sublist | + sublist.getTail() = mro and + interesting_class_attribute(sublist, name) + ) + or + name = "__call__" + } + + private predicate does_not_have_attribute(ClassList mro, string name) { + interesting_class_attribute(mro, name) and + ( + mro.isEmpty() + or + exists(ClassObject head, ClassList tail | + head = mro.getHead() and tail = mro.getTail() | + does_not_have_attribute(tail, name) and + not class_declares_attribute(head, name) + ) + ) + } + + /** Holds if the class `cls` has an attribute called `name` */ + cached predicate class_has_attribute(ClassObject cls, string name) { + class_declares_attribute(get_an_improper_super_type(cls), name) + } + + /** Gets `true` if the class `cls` is known to have attribute `name`, + * or `false` if the class `cls` is known to not have attribute `name`. + */ + cached boolean class_has_attribute_bool(ClassObject cls, string name) { + exists(ClassList mro | + mro = cls.getMro() | + mro.declares(name) and result = true + or + does_not_have_attribute(mro, name) and result = false + ) + } + + /** INTERNAL -- Use `ClassObject.attributeRefersTo(name, value, vlcs, origin). instead. + */ + cached predicate class_attribute_lookup(ClassObject cls, string name, Object value, ClassObject vcls, ObjectOrCfg origin) { + exists(ClassObject defn | + defn = get_mro(cls).findDeclaringClass(name) and + class_declared_attribute(defn, name, value, vcls, origin) + ) + } + + /** INTERNAL -- Use `ClassObject.failedInference(reason). instead. + * + * Holds if type inference failed to compute the full class hierarchy for this class for the reason given. */ + cached predicate failed_inference(ClassObject cls, string reason) { + strictcount(cls.getPyClass().getADecorator()) > 1 and reason = "Multiple decorators" + or + exists(cls.getPyClass().getADecorator()) and not six_add_metaclass(_, cls, _) and reason = "Decorator not understood" + or + exists(int i | + exists(((ClassExpr)cls.getOrigin()).getBase(i)) and reason = "Missing base " + i + | + not exists(class_base_type(cls, i)) + ) + or + exists(cls.getPyClass().getMetaClass()) and not exists(class_get_meta_class(cls)) and reason = "Failed to infer metaclass" + or + exists(int i | failed_inference(class_base_type(cls, i), _) and reason = "Failed inference for base class at position " + i) + or + exists(int i | strictcount(class_base_type(cls, i)) > 1 and reason = "Multiple bases at position " + i) + or + exists(int i, int j | class_base_type(cls, i) = class_base_type(cls, j) and i != j and reason = "Duplicate bases classes") + or + cls = theUnknownType() and reason = "Unknown Type" + } + + /** INTERNAL -- Use `ClassObject.getMetaClass()` instead. + * + * Gets the metaclass for this class */ + cached ClassObject class_get_meta_class(ClassObject cls) { + result = declared_meta_class(cls) + or + has_declared_metaclass(cls) = false and result = get_inherited_metaclass(cls) + or + cls = theUnknownType() and result = theUnknownType() + } + + private ClassObject declared_meta_class(ClassObject cls) { + exists(Object obj | + ssa_variable_points_to(metaclass_var(cls), _, obj, _, _) | + result = obj + or + obj = unknownValue() and result = theUnknownType() + ) + or + py_cobjecttypes(cls, result) and is_c_metaclass(result) + or + exists(ControlFlowNode meta | + Types::six_add_metaclass(_, cls, meta) and + points_to(meta, _, result, _, _) + ) + } + + private boolean has_metaclass_var_metaclass(ClassObject cls) { + exists(Object obj | + ssa_variable_points_to(metaclass_var(cls), _, obj, _, _) | + obj = undefinedVariable() and result = false + or + obj != undefinedVariable() and result = true + ) + or + not exists(metaclass_var(cls)) and result = false + } + + private boolean has_declared_metaclass(ClassObject cls) { + py_cobjecttypes(cls, _) and result = true + or + not cls.isBuiltin() and + result = has_six_add_metaclass(cls).booleanOr(has_metaclass_var_metaclass(cls)) + } + + private EssaVariable metaclass_var(ClassObject cls) { + result.getASourceUse() = cls.getPyClass().getMetaClass().getAFlowNode() + or + major_version() = 2 and not exists(cls.getPyClass().getMetaClass()) and + result.getName() = "__metaclass__" and + cls.getPyClass().(ImportTimeScope).entryEdge(result.getAUse(), _) + } + + private ClassObject get_inherited_metaclass(ClassObject cls) { + result = get_inherited_metaclass(cls, 0) + or + // Best guess if base is not a known class + exists(Object base | + base = class_base_type(cls, _) and + result = theUnknownType() | + not base instanceof ClassObject + or + base = theUnknownType() + ) + } + + private ClassObject get_inherited_metaclass(ClassObject cls, int n) { + exists(Class c | + c = cls.getPyClass() and + n = count(c.getABase()) + | + major_version() = 3 and result = theTypeType() + or + major_version() = 2 and result = theClassType() + ) + or + exists(ClassObject meta1, ClassObject meta2 | + meta1 = class_get_meta_class(py_base_type(cls, n)) and + meta2 = get_inherited_metaclass(cls, n+1) + | + /* Choose sub-class */ + get_an_improper_super_type(meta1) = meta2 and result = meta1 + or + get_an_improper_super_type(meta2) = meta1 and result = meta2 + or + /* Choose new-style meta-class over old-style */ + meta2 = theClassType() and result = meta1 + or + /* Make sure we have a metaclass, even if base is unknown */ + meta1 = theUnknownType() and result = theTypeType() + or + meta2 = theUnknownType() and result = meta1 + ) + } + + private Object six_add_metaclass_function() { + exists(Module six, FunctionExpr add_metaclass | + add_metaclass.getInnerScope().getName() = "add_metaclass" and + add_metaclass.getScope() = six and + result.getOrigin() = add_metaclass + ) + } + + private ControlFlowNode decorator_call_callee(ClassObject cls) { + exists(CallNode decorator_call, CallNode decorator | + decorator_call.getArg(0) = cls and + decorator = decorator_call.getFunction() and + result = decorator.getFunction() + ) + } + + /** INTERNAL -- Do not use */ + cached boolean has_six_add_metaclass(ClassObject cls) { + exists(ControlFlowNode callee, Object func | + callee = decorator_call_callee(cls) and + points_to(callee, _, func, _, _) | + func = six_add_metaclass_function() and result = true + or + not func = six_add_metaclass_function() and result = false + ) + or + not exists(six_add_metaclass_function()) and result = false + or + not exists(decorator_call_callee(cls)) and result = false + } + + /** INTERNAL -- Do not use */ + cached predicate six_add_metaclass(CallNode decorator_call, ClassObject decorated, ControlFlowNode metaclass) { + exists(CallNode decorator | + decorator_call.getArg(0) = decorated and + decorator = decorator_call.getFunction() and + decorator.getArg(0) = metaclass | + points_to(decorator.getFunction(), _, six_add_metaclass_function(), _, _) + or + exists(ModuleObject six | + six.getName() = "six" and + points_to(decorator.getFunction().(AttrNode).getObject("add_metaclass"), _, six, _, _) + ) + ) + } + + /** INTERNAL -- Use `not cls.isAbstract()` instead. */ + cached predicate concrete_class(ClassObject cls) { + Types::class_get_meta_class(cls) != theAbcMetaClassObject() + or + exists(Class c | + c = cls.getPyClass() and + not exists(c.getMetaClass()) + | + forall(Function f | + f.getScope() = c | + not exists(Raise r, Name ex | + r.getScope() = f and + (r.getException() = ex or r.getException().(Call).getFunc() = ex) and + (ex.getId() = "NotImplementedError" or ex.getId() = "NotImplemented") + ) + ) + ) + } + + /** Holds if instances of class `cls` are always truthy. */ + cached predicate instances_always_true(ClassObject cls) { + cls = theObjectType() + or + instances_always_true(cls, 0) and + not exists(string meth | + class_declares_attribute(cls, meth) | + meth = "__bool__" or meth = "__len__" or + meth = "__nonzero__" and major_version() = 2 + ) + } + + /** Holds if instances of class `cls` are always truthy. */ + cached predicate instances_always_true(ClassObject cls, int n) { + not cls = theNoneType() and + n = class_base_count(cls) + or + instances_always_true(cls, n+1) and + instances_always_true(class_base_type(cls, n)) + } + + } + + /** INTERNAL -- Public for testing only */ + module Test { + + import Calls + import SSA + import Layer + + } + +} + +/* Helper classes for `super` dispatching. */ + +class SuperCall extends Object { + + EssaVariable self; + ClassObject start; + + override string toString() { + result = "super()" + } + + SuperCall() { + exists(CallNode call, PointsToContext context | + call = this and + PointsTo::points_to(call.getFunction(), _, theSuperType(), _, _) | + PointsTo::points_to(call.getArg(0), context, start, _, _) and + self.getASourceUse() = call.getArg(1) + or + major_version() = 3 and + not exists(call.getArg(0)) and + exists(Function func | + call.getScope() = func and + context.appliesToScope(func) and + /* Implicit class argument is lexically enclosing scope */ + func.getScope() = start.getPyClass() and + /* Implicit 'self' is the 0th parameter */ + self.getDefinition().(ParameterDefinition).getDefiningNode() = func.getArg(0).asName().getAFlowNode() + ) + ) + } + + ClassObject startType() { + result = start + } + + ClassObject selfType(PointsToContext ctx) { + PointsTo::ssa_variable_points_to(self, ctx, _, result, _) + } + + predicate instantiation(PointsToContext ctx, ControlFlowNode f) { + PointsTo::points_to(this.(CallNode).getArg(0), ctx, start, _, _) and f = this + } + + EssaVariable getSelf() { + result = self + } +} + +class SuperBoundMethod extends Object { + + override string toString() { + result = "super()." + name + } + + SuperCall superObject; + string name; + + cached + SuperBoundMethod() { + exists(ControlFlowNode object | + this.(AttrNode).getObject(name) = object | + PointsTo::points_to(object, _, superObject, _, _) + ) + } + + FunctionObject getFunction(PointsToContext ctx) { + exists(ClassList mro | + mro = PointsTo::Types::get_mro(superObject.selfType(ctx)) | + result = mro.startingAt(superObject.startType()).getTail().lookup(name) + ) + } + + predicate instantiation(PointsToContext ctx, ControlFlowNode f) { + PointsTo::points_to(this.(AttrNode).getObject(name), ctx, superObject, _, _) and f = this + } + + EssaVariable getSelf() { + result = superObject.getSelf() + } + +} + diff --git a/python/ql/src/semmle/python/pointsto/PointsToContext.qll b/python/ql/src/semmle/python/pointsto/PointsToContext.qll new file mode 100755 index 000000000000..abbf5117e666 --- /dev/null +++ b/python/ql/src/semmle/python/pointsto/PointsToContext.qll @@ -0,0 +1,276 @@ +import python +private import semmle.python.pointsto.PointsTo + +/* + * A note on 'cost'. Cost doesn't represent the cost to compute, + * but (a vague estimate of) the cost to compute per value gained. + * This is constantly evolving, so see the various cost functions below for more details. + */ + +private int given_cost() { + exists(string depth | + py_flags_versioned("context.cost", depth, _) and + result = depth.toInt() + ) +} + +private int max_context_cost() { + not py_flags_versioned("context.cost", _, _) and result = 7 + or + result = max(int cost | cost = given_cost() | cost) +} + +private int syntactic_call_count(Scope s) { + exists(Function f | + f = s and f.getName() != "__init__" | + result = count(CallNode call | + call.getFunction().(NameNode).getId() = f.getName() + or + call.getFunction().(AttrNode).getName() = f.getName() + ) + ) + or + s.getName() = "__init__" and result = 1 + or + not s instanceof Function and result = 0 +} + +private int incoming_call_cost(Scope s) { + /* Syntactic call count will often be a considerable overestimate + * of the actual number of calls, so we use the square root. + * Cost = log(sqrt(call-count)) + */ + result = ((syntactic_call_count(s)+1).log(2)*0.5).floor() +} + +private int context_cost(TPointsToContext ctx) { + ctx = TMainContext() and result = 0 + or + ctx = TRuntimeContext() and result = 0 + or + ctx = TImportContext() and result = 0 + or + ctx = TCallContext(_, _, result) +} + +private int call_cost(CallNode call) { + if call.getScope().inSource() then + result = 2 + else + result = 3 +} + +private int outgoing_calls(Scope s) { + result = strictcount(CallNode call | call.getScope() = s) +} + +predicate super_method_call(CallNode call) { + call.getFunction().(AttrNode).getObject().(CallNode).getFunction().(NameNode).getId() = "super" +} + +private int outgoing_call_cost(CallNode c) { + /* Cost = log(outgoing-call-count) */ + result = outgoing_calls(c.getScope()).log(2).floor() +} + +/** Cost of contexts for a call, the more callers the + * callee of call has the more expensive it is to add contexts for it. + * This seems to be an effective heuristics for preventing an explosion + * in the number of contexts while retaining good results. + */ +private int splay_cost(CallNode c) { + if super_method_call(c) then + result = 0 + else + result = outgoing_call_cost(c) + incoming_call_cost(c.getScope()) +} + +private predicate call_to_init_or_del(CallNode call) { + exists(string mname | + mname = "__init__" or mname = "__del__" | + mname = call.getFunction().(AttrNode).getName() + ) +} + +/** Total cost estimate */ +private int total_call_cost(CallNode call) { + /* We want to always follow __init__ and __del__ calls as they tell us about object construction, + * but we need to be aware of cycles, so they must have a non-zero cost. + */ + if call_to_init_or_del(call) then + result = 1 + else + result = call_cost(call) + splay_cost(call) +} + +private int total_cost(CallNode call, PointsToContext ctx) { + ctx.appliesTo(call) and + result = total_call_cost(call) + context_cost(ctx) +} + +private cached newtype TPointsToContext = + TMainContext() + or + TRuntimeContext() + or + TImportContext() + or + TCallContext(ControlFlowNode call, PointsToContext outerContext, int cost) { + total_cost(call, outerContext) = cost and + cost <= max_context_cost() + } + +/** Points-to context. Context can be one of: + * * "main": Used for scripts. + * * "import": Use for non-script modules. + * * "default": Use for functions and methods without caller context. + * * All other contexts are call contexts and consist of a pair of call-site and caller context. + */ +class PointsToContext extends TPointsToContext { + + cached string toString() { + this = TMainContext() and result = "main" + or + this = TRuntimeContext() and result = "runtime" + or + this = TImportContext() and result = "import" + or + exists(CallNode callsite, PointsToContext outerContext | + this = TCallContext(callsite, outerContext, _) and + result = callsite.getLocation() + " from " + outerContext.toString() + ) + } + + /** Holds if `call` is the call-site from which this context was entered and `outer` is the caller's context. */ + predicate fromCall(CallNode call, PointsToContext caller) { + caller.appliesTo(call) and + this = TCallContext(call, caller, _) + } + + /** Holds if `call` is the call-site from which this context was entered and `caller` is the caller's context. */ + predicate fromCall(CallNode call, FunctionObject callee, PointsToContext caller) { + call = PointsTo::get_a_call(callee, caller) and + this = TCallContext(call, caller, _) + } + + /** Gets the caller context for this callee context. */ + PointsToContext getOuter() { + this = TCallContext(_, result, _) + } + + /** Holds if this context is relevant to the given scope. */ + predicate appliesToScope(Scope s) { + /* Scripts */ + this = TMainContext() and maybe_main(s) + or + /* Modules and classes evaluated at import */ + s instanceof ImportTimeScope and this = TImportContext() + or + this = TRuntimeContext() and executes_in_runtime_context(s) + or + /* Called functions, regardless of their name */ + exists(FunctionObject func, ControlFlowNode call, TPointsToContext outerContext | + call = PointsTo::get_a_call(func, outerContext) and + this = TCallContext(call, outerContext, _) and + s = func.getFunction() + ) + or + exists(FunctionObject func | + PointsTo::Flow::callsite_calls_function(_, _, func, this, _) and + s = func.getFunction() + ) + } + + /** Holds if this context can apply to the CFG node `n`. */ + pragma [inline] + predicate appliesTo(ControlFlowNode n) { + this.appliesToScope(n.getScope()) + } + + /** Holds if this context is a call context. */ + predicate isCall() { + this = TCallContext(_, _, _) + } + + /** Holds if this is the "main" context. */ + predicate isMain() { + this = TMainContext() + } + + /** Holds if this is the "import" context. */ + predicate isImport() { + this = TImportContext() + } + + /** Holds if this is the "default" context. */ + predicate isRuntime() { + this = TRuntimeContext() + } + + /** Holds if this context or one of its caller contexts is the default context. */ + predicate fromRuntime() { + this.isRuntime() + or + this.getOuter().fromRuntime() + } + + /** Gets the depth (number of calls) for this context. */ + int getDepth() { + not exists(this.getOuter()) and result = 0 + or + result = this.getOuter().getDepth() + 1 + } + + int getCost() { + result = context_cost(this) + } + + /** Holds if a call would be too expensive to create a new context for */ + predicate untrackableCall(CallNode call) { + total_cost(call, this) > max_context_cost() + } + + CallNode getRootCall() { + this = TCallContext(result, TImportContext(), _) + or + result = this.getOuter().getRootCall() + } + + /** Gets a version of Python that this context includes */ + pragma [inline] + Version getAVersion() { + /* Currently contexts do not include any version information, but may do in the future */ + result = major_version() + } + +} + +private predicate in_source(Scope s) { + exists(s.getEnclosingModule().getFile().getRelativePath()) +} + +/** Holds if this scope can be executed in the default context. + * All modules and classes executed at import time and + * all "public" functions and methods, including those invoked by the VM. + */ +predicate executes_in_runtime_context(Function f) { + /* "Public" scope, i.e. functions whose name starts not with an underscore, or special methods */ + (f.getName().charAt(0) != "_" or f.isSpecialMethod() or f.isInitMethod()) + and + in_source(f) +} + +private predicate maybe_main(Module m) { + exists(If i, Compare cmp, Name name, StrConst main | + m.getAStmt() = i and i.getTest() = cmp | + cmp.compares(name, any(Eq eq), main) and + name.getId() = "__name__" and + main.getText() = "__main__" + ) +} + + +/* For backwards compatibility */ +/** DEPRECATED: Use `PointsToContext` instead */ +deprecated class FinalContext = PointsToContext; + diff --git a/python/ql/src/semmle/python/protocols.qll b/python/ql/src/semmle/python/protocols.qll new file mode 100644 index 000000000000..31808ff3c53e --- /dev/null +++ b/python/ql/src/semmle/python/protocols.qll @@ -0,0 +1,31 @@ +import python + +/** Retained for backwards compatibility use ClassObject.isIterator() instead. */ +predicate is_iterator(ClassObject c) { + c.isIterator() +} + +/** Retained for backwards compatibility use ClassObject.isIterable() instead. */ +predicate is_iterable(ClassObject c) { + c.isIterable() +} + +/** Retained for backwards compatibility use ClassObject.isCollection() instead. */ +predicate is_collection(ClassObject c) { + c.isCollection() +} + +/** Retained for backwards compatibility use ClassObject.isMapping() instead. */ +predicate is_mapping(ClassObject c) { + c.isMapping() +} + +/** Retained for backwards compatibility use ClassObject.isSequence() instead. */ +predicate is_sequence(ClassObject c) { + c.isSequence() +} + +/** Retained for backwards compatibility use ClassObject.isContextManager() instead. */ +predicate is_context_manager(ClassObject c) { + c.isContextManager() +} diff --git a/python/ql/src/semmle/python/regex.qll b/python/ql/src/semmle/python/regex.qll new file mode 100644 index 000000000000..0636c485f060 --- /dev/null +++ b/python/ql/src/semmle/python/regex.qll @@ -0,0 +1,709 @@ +import python + +private predicate re_module_function(string name, int flags) { + name = "compile" and flags = 1 or + name = "search" and flags = 2 or + name = "match" and flags = 2 or + name = "split" and flags = 3 or + name = "findall" and flags = 2 or + name = "finditer" and flags = 2 or + name = "sub" and flags = 4 or + name = "subn" and flags = 4 +} + +predicate used_as_regex(Expr s, string mode) { + (s instanceof Bytes or s instanceof Unicode) + and + exists(ModuleObject re | re.getName() = "re" | + /* Call to re.xxx(regex, ... [mode]) */ + exists(CallNode call, string name | + call.getArg(0).refersTo(_, _, s.getAFlowNode()) and + call.getFunction().refersTo(re.getAttribute(name)) | + mode = "None" + or + exists(Object obj | + mode = mode_from_mode_object(obj) | + exists(int flags_arg | + re_module_function(name, flags_arg) and + call.getArg(flags_arg).refersTo(obj) + ) + or + call.getArgByName("flags").refersTo(obj) + ) + ) + ) +} + +string mode_from_mode_object(Object obj) { + ( + result = "DEBUG" or result = "IGNORECASE" or result = "LOCALE" or + result = "MULTILINE" or result = "DOTALL" or result = "UNICODE" or + result = "VERBOSE" + ) and + exists(ModuleObject re | re.getName() = "re" and re.getAttribute(result) = obj) + or + exists(BinaryExpr be, Object sub | obj.getOrigin() = be | + be.getOp() instanceof BitOr and + be.getASubExpression().refersTo(sub) and + result = mode_from_mode_object(sub) + ) +} + +/** A StrConst used as a regular expression */ +abstract class RegexString extends Expr { + + RegexString() { + (this instanceof Bytes or this instanceof Unicode) + } + + predicate char_set_start(int start, int end) { + this.nonEscapedCharAt(start) = "[" and + ( + this.getChar(start+1) = "^" and end = start + 2 + or + not this.getChar(start+1) = "^" and end = start + 1 + ) + } + + /** Whether there is a character class, between start (inclusive) and end (exclusive) */ + predicate charSet(int start, int end) { + exists(int inner_start, int inner_end | + this.char_set_start(start, inner_start) | + end = inner_end + 1 and inner_end > inner_start and + this.nonEscapedCharAt(inner_end) = "]" and + not exists(int mid | this.nonEscapedCharAt(mid) = "]" | + mid > inner_start and mid < inner_end + ) + ) + } + + predicate escapingChar(int pos) { + this.escaping(pos) = true + } + + private boolean escaping(int pos) { + pos = -1 and result = false + or + this.getChar(pos) = "\\" and result = this.escaping(pos-1).booleanNot() + or + this.getChar(pos) != "\\" and result = false + } + + /** Gets the text of this regex */ + string getText() { + result = ((Unicode)this).getS() + or + result = ((Bytes)this).getS() + } + + string getChar(int i) { + result = this.getText().charAt(i) + } + + string nonEscapedCharAt(int i) { + result = this.getText().charAt(i) and + not this.escapingChar(i-1) + } + + private predicate isOptionDivider(int i) { + this.nonEscapedCharAt(i) = "|" + } + + private predicate isGroupEnd(int i) { + this.nonEscapedCharAt(i) = ")" + } + + private predicate isGroupStart(int i) { + this.nonEscapedCharAt(i) = "(" + } + + predicate failedToParse(int i) { + exists(this.getChar(i)) + and + not exists(int start, int end | + this.top_level(start, end) and + start <= i and + end > i + ) + } + + private predicate escapedCharacter(int start, int end) { + this.escapingChar(start) and not exists(this.getText().substring(start+1, end+1).toInt()) and + ( + this.getChar(start+1) = "x" and end = start + 4 + or + end in [start+2..start+4] and + exists(this.getText().substring(start+1, end).toInt()) + or + this.getChar(start+1) != "x" and end = start + 2 + ) + } + + private predicate inCharSet(int index) { + exists(int x, int y | this.charSet(x, y) and index in [x+1 .. y-2]) + } + + /* 'simple' characters are any that don't alter the parsing of the regex. + */ + private predicate simpleCharacter(int start, int end) { + end = start+1 and + not this.charSet(start, _) and + not this.charSet(_, start+1) and + exists(string c | + c = this.getChar(start) | + exists(int x, int y, int z | + this.charSet(x, z) and + this.char_set_start(x, y) | + start = y + or + start = z-2 + or + start > y and start < z-2 and not c = "-" + ) + or + not this.inCharSet(start) and + not c = "(" and not c = "[" and + not c = ")" and not c = "|" and + not this.qualifier(start, _, _) + ) + } + + predicate character(int start, int end) { + ( + this.simpleCharacter(start, end) and + not exists(int x, int y | this.escapedCharacter(x, y) and x <= start and y >= end) + or + this.escapedCharacter(start, end) + ) + and + not exists(int x, int y | + this.group_start(x, y) and x <= start and y >= end + ) + } + + predicate normalCharacter(int start, int end) { + this.character(start, end) + and + not this.specialCharacter(start, end, _) + } + + predicate specialCharacter(int start, int end, string char) { + this.character(start, end) + and + end = start+1 + and + char = this.getChar(start) + and + (char = "$" or char = "^" or char = ".") + and + not this.inCharSet(start) + } + + /** Whether the text in the range start,end is a group */ + predicate group(int start, int end) { + this.groupContents(start, end, _, _) + or + this.emptyGroup(start, end) + } + + /** Gets the number of the group in start,end */ + int getGroupNumber(int start, int end) { + this.group(start, end) and + result = count(int i | this.group(i, _) and i < start and not this.non_capturing_group_start(i, _)) + 1 + } + + /** Gets the name, if it has one, of the group in start,end */ + string getGroupName(int start, int end) { + this.group(start, end) + and + exists(int name_end | + this.named_group_start(start, name_end) and + result = this.getText().substring(start+4, name_end-1) + ) + } + + /** Whether the text in the range start, end is a group and can match the empty string. */ + predicate zeroWidthMatch(int start, int end) { + this.emptyGroup(start, end) + or + this.negativeAssertionGroup(start, end) + or + positiveLookaheadAssertionGroup(start, end) + or + this.positiveLookbehindAssertionGroup(start, end) + } + + private predicate emptyGroup(int start, int end) { + exists(int endm1 | + end = endm1+1 | + this.group_start(start, endm1) and + this.isGroupEnd(endm1) + ) + } + + private predicate emptyMatchAtStartGroup(int start, int end) { + this.emptyGroup(start, end) + or + this.negativeAssertionGroup(start, end) + or + this.positiveLookaheadAssertionGroup(start, end) + } + + private predicate emptyMatchAtEndGroup(int start, int end) { + this.emptyGroup(start, end) + or + this.negativeAssertionGroup(start, end) + or + this.positiveLookbehindAssertionGroup(start, end) + } + + private predicate negativeAssertionGroup(int start, int end) { + exists(int in_start | + this.negative_lookahead_assertion_start(start, in_start) + or + this.negative_lookbehind_assertion_start(start, in_start) | + this.groupContents(start, end, in_start, _) + ) + } + + private predicate positiveLookaheadAssertionGroup(int start, int end) { + exists(int in_start | + this.lookahead_assertion_start(start, in_start) | + this.groupContents(start, end, in_start, _) + ) + } + + private predicate positiveLookbehindAssertionGroup(int start, int end) { + exists(int in_start | + this.lookbehind_assertion_start(start, in_start) | + this.groupContents(start, end, in_start, _) + ) + } + + private predicate group_start(int start, int end) { + this.non_capturing_group_start(start, end) + or + this.flag_group_start(start, end, _) + or + this.named_group_start(start, end) + or + this.named_backreference_start(start, end) + or + this.lookahead_assertion_start(start, end) + or + this.negative_lookahead_assertion_start(start, end) + or + this.lookbehind_assertion_start(start, end) + or + this.negative_lookbehind_assertion_start(start, end) + or + this.comment_group_start(start, end) + or + this.simple_group_start(start, end) + } + + private predicate non_capturing_group_start(int start, int end) { + this.isGroupStart(start) and + this.getChar(start+1) = "?" and + this.getChar(start+2) = ":" and + end = start+3 + } + + private predicate simple_group_start(int start, int end) { + this.isGroupStart(start) and + this.getChar(start+1) != "?" and end = start+1 + } + + private predicate named_group_start(int start, int end) { + this.isGroupStart(start) and + this.getChar(start+1) = "?" and + this.getChar(start+2) = "P" and + this.getChar(start+3) = "<" and + not this.getChar(start+4) = "=" and + not this.getChar(start+4) = "!" and + exists(int name_end | + name_end = min(int i | i > start+4 and this.getChar(i) = ">") and + end = name_end + 1 + ) + } + + private predicate named_backreference_start(int start, int end) { + this.isGroupStart(start) and + this.getChar(start+1) = "?" and + this.getChar(start+2) = "P" and + this.getChar(start+3) = "=" and + end = min(int i | i > start+4 and this.getChar(i) = "?") + } + + private predicate flag_group_start(int start, int end, string c) { + this.isGroupStart(start) and + this.getChar(start+1) = "?" and + end = start+3 and + c = this.getChar(start+2) and + ( + c = "i" or + c = "L" or + c = "m" or + c = "s" or + c = "u" or + c = "x" + ) + } + + /** Gets the mode of this regular expression string if + * it is defined by a prefix. + */ + string getModeFromPrefix() { + exists(string c | + this.flag_group_start(_, _, c) | + c = "i" and result = "IGNORECASE" + or + c = "L" and result = "LOCALE" + or + c = "m" and result = "MULTILINE" + or + c = "s" and result = "DOTALL" + or + c = "u" and result = "UNICODE" + or + c = "x" and result = "VERBOSE" + ) + } + + private predicate lookahead_assertion_start(int start, int end) { + this.isGroupStart(start) and + this.getChar(start+1) = "?" and + this.getChar(start+2) = "=" and + end = start+3 + } + + private predicate negative_lookahead_assertion_start(int start, int end) { + this.isGroupStart(start) and + this.getChar(start+1) = "?" and + this.getChar(start+2) = "!" and + end = start+3 + } + + private predicate lookbehind_assertion_start(int start, int end) { + this.isGroupStart(start) and + this.getChar(start+1) = "?" and + this.getChar(start+2) = "<" and + this.getChar(start+3) = "=" and + end = start+4 + } + + private predicate negative_lookbehind_assertion_start(int start, int end) { + this.isGroupStart(start) and + this.getChar(start+1) = "?" and + this.getChar(start+2) = "<" and + this.getChar(start+3) = "!" and + end = start+4 + } + + private predicate comment_group_start(int start, int end) { + this.isGroupStart(start) and + this.getChar(start+1) = "?" and + this.getChar(start+2) = "#" and + end = start+3 + } + + predicate groupContents(int start, int end, int in_start, int in_end) { + this.group_start(start, in_start) and + end = in_end + 1 and + this.top_level(in_start, in_end) and + this.isGroupEnd(in_end) + } + + private predicate named_backreference(int start, int end, string name) { + this.named_backreference_start(start, start+4) and + end = min(int i | i > start+4 and this.getChar(i) = ")") + 1 and + name = this.getText().substring(start+4, end-2) + } + + private predicate numbered_backreference(int start, int end, int value) { + this.escapingChar(start) + and + exists(string text, string svalue, int len | + end = start + len and + text = this.getText() and len in [2..3] | + svalue = text.substring(start+1, start+len) and + value = svalue.toInt() and + not exists(text.substring(start+1, start+len+1).toInt()) and + value != 0 + ) + } + + /** Whether the text in the range start,end is a back reference */ + predicate backreference(int start, int end) { + this.numbered_backreference(start, end, _) + or + this.named_backreference(start, end, _) + } + + /** Gets the number of the back reference in start,end */ + int getBackrefNumber(int start, int end) { + this.numbered_backreference(start, end, result) + } + + /** Gets the name, if it has one, of the back reference in start,end */ + string getBackrefName(int start, int end) { + this.named_backreference(start, end, result) + } + + private predicate baseItem(int start, int end) { + this.character(start, end) and not exists(int x, int y | this.charSet(x, y) and x <= start and y >= end) + or + this.group(start, end) + or + this.charSet(start, end) + } + + private predicate qualifier(int start, int end, boolean maybe_empty) { + this.short_qualifier(start, end, maybe_empty) and not this.getChar(end) = "?" + or + exists(int short_end | + this.short_qualifier(start, short_end, maybe_empty) | + if this.getChar(short_end) = "?" then + end = short_end+1 + else + end = short_end + ) + } + + private predicate short_qualifier(int start, int end, boolean maybe_empty) { + ( + this.getChar(start) = "+" and maybe_empty = false + or + this.getChar(start) = "*" and maybe_empty = true + or + this.getChar(start) = "?" and maybe_empty = true + ) and end = start + 1 + or + exists(int endin | end = endin + 1 | + this.getChar(start) = "{" and this.getChar(endin) = "}" and + end > start and + exists(string multiples | + multiples = this.getText().substring(start+1, endin) | + multiples.regexpMatch("0*,[0-9]*") and maybe_empty = true + or + multiples.regexpMatch("0*[1-9][0-9]*,[0-9]*") and maybe_empty = false + ) + and + not exists(int mid | + this.getChar(mid) = "}" and + mid > start and mid < endin + ) + ) + } + + /** Whether the text in the range start,end is a qualified item, where item is a character, + * a character set or a group. + */ + predicate qualifiedItem(int start, int end, boolean maybe_empty) { + this.qualifiedPart(start, _, end, maybe_empty) + } + + private predicate qualifiedPart(int start, int part_end, int end, boolean maybe_empty) { + this.baseItem(start, part_end) and + this.qualifier(part_end, end, maybe_empty) + } + + private predicate item(int start, int end) { + this.qualifiedItem(start, end, _) + or + this.baseItem(start, end) and not this.qualifier(end, _, _) + } + + private predicate subsequence(int start, int end) { + ( + start = 0 or + this.group_start(_, start) or + this.isOptionDivider(start-1) + ) + and + this.item(start, end) or + ( + exists(int mid | + this.subsequence(start, mid) and + this.item(mid, end) + ) + ) + } + + /** Whether the text in the range start,end is a sequence of 1 or more items, where an item is a character, + * a character set or a group. + */ + predicate sequence(int start, int end) { + this.sequenceOrQualified(start, end) and + not this.qualifiedItem(start, end, _) + } + + private predicate sequenceOrQualified(int start, int end) { + this.subsequence(start, end) and + not this.item_start(end) + } + + private predicate item_start(int start) { + this.character(start, _) or + this.isGroupStart(start) or + this.charSet(start, _) + } + + private predicate item_end(int end) { + this.character(_, end) or + exists(int endm1 | this.isGroupEnd(endm1) and end = endm1 + 1) or + this.charSet(_, end) or + this.qualifier(_, end, _) + } + + private predicate top_level(int start, int end) { + this.subalternation(start, end, _) and + not this.isOptionDivider(end) + } + + private predicate subalternation(int start, int end, int item_start) { + this.sequenceOrQualified(start, end) and not this.isOptionDivider(start-1) and + item_start = start + or + exists(int endp1 | end = endp1-1 | + start = end and not this.item_end(start) and this.isOptionDivider(endp1) and + item_start = start + ) + or + exists(int mid | + this.subalternation(start, mid, _) and + this.isOptionDivider(mid) and + item_start = mid+1 | + this.sequenceOrQualified(item_start, end) + or + not this.item_start(end) and end = item_start + ) + } + + /** Whether the text in the range start,end is an alternation + */ + predicate alternation(int start, int end) { + this.top_level(start, end) and + exists(int less | this.subalternation(start, less, _) and less < end) + } + + /** Whether the text in the range start,end is an alternation and the text in part_start, part_end is one of the + * options in that alternation. + */ + predicate alternationOption(int start, int end, int part_start, int part_end) { + this.alternation(start, end) and + this.subalternation(start, part_end, part_start) + } + + /** A part of the regex that may match the start of the string. */ + private predicate firstPart(int start, int end) { + start = 0 and end = this.getText().length() + or + exists(int x | + this.firstPart(x, end) | + this.emptyMatchAtStartGroup(x, start) or + this.qualifiedItem(x, start, true) or + this.specialCharacter(x, start, "^") + ) + or + exists(int y | + this.firstPart(start, y) | + this.item(start, end) + or + this.qualifiedPart(start, end, y, _) + ) + or + exists(int x, int y | + this.firstPart(x, y) | + this.groupContents(x, y, start, end) + or + this.alternationOption(x, y, start, end) + ) + } + + /** A part of the regex that may match the end of the string. */ + private predicate lastPart(int start, int end) { + start = 0 and end = this.getText().length() + or + exists(int y | + this.lastPart(start, y) | + this.emptyMatchAtEndGroup(end, y) or + this.qualifiedItem(end, y, true) or + this.specialCharacter(end, y, "$") + ) + or + exists(int x | + this.lastPart(x, end) and + this.item(start, end) + ) + or + exists(int y | + this.lastPart(start, y) | + this.qualifiedPart(start, end, y, _) + ) + or + exists(int x, int y | + this.lastPart(x, y) | + this.groupContents(x, y, start, end) + or + this.alternationOption(x, y, start, end) + ) + } + + /** Whether the item at [start, end) is one of the first items + * to be matched. + */ + predicate firstItem(int start, int end) { + ( + this.character(start, end) + or + this.qualifiedItem(start, end, _) + or + this.charSet(start, end) + ) + and + this.firstPart(start, end) + } + + /** Whether the item at [start, end) is one of the last items + * to be matched. + */ + predicate lastItem(int start, int end) { + ( + this.character(start, end) + or + this.qualifiedItem(start, end, _) + or + this.charSet(start, end) + ) + and + this.lastPart(start, end) + } + +} + + +/** A StrConst used as a regular expression */ +class Regex extends RegexString { + + Regex() { + used_as_regex(this, _) + } + + /** Gets a mode (if any) of this regular expression. Can be any of: + * DEBUG + * IGNORECASE + * LOCALE + * MULTILINE + * DOTALL + * UNICODE + * VERBOSE + */ + string getAMode() { + result != "None" and + used_as_regex(this, result) + or + result = this.getModeFromPrefix() + } + +} \ No newline at end of file diff --git a/python/ql/src/semmle/python/security/Crypto.qll b/python/ql/src/semmle/python/security/Crypto.qll new file mode 100644 index 000000000000..38ce0fa35412 --- /dev/null +++ b/python/ql/src/semmle/python/security/Crypto.qll @@ -0,0 +1,195 @@ +import python +import semmle.python.security.TaintTracking + +private import semmle.python.security.SensitiveData +private import semmle.crypto.Crypto as CryptoLib + + +abstract class WeakCryptoSink extends TaintSink { + + override predicate sinks(TaintKind taint) { + taint instanceof SensitiveData + } +} + +module Pycrypto { + + ModuleObject cipher(string name) { + exists(PackageObject crypto | + crypto.getName() = "Crypto.Cipher" | + crypto.submodule(name) = result + ) + } + + class CipherInstance extends TaintKind { + + string name; + + CipherInstance() { + this = "Crypto.Cipher." + name and + exists(cipher(name)) + } + + string getName() { + result = name + } + + CryptoLib::CryptographicAlgorithm getAlgorithm() { + result.getName() = name + } + + predicate isWeak() { + this.getAlgorithm().isWeak() + } + + } + + class CipherInstanceSource extends TaintSource { + + CipherInstance instance; + + CipherInstanceSource() { + exists(AttrNode attr | + this.(CallNode).getFunction() = attr and + attr.getObject("new").refersTo(cipher(instance.getName())) + ) + } + + override string toString() { + result = "Source of " + instance + } + + override predicate isSourceOf(TaintKind kind) { + kind = instance + } + + } + + class PycryptoWeakCryptoSink extends WeakCryptoSink { + + string name; + + PycryptoWeakCryptoSink() { + exists(CallNode call, AttrNode method, CipherInstance Cipher | + call.getAnArg() = this and + call.getFunction() = method and + Cipher.taints(method.getObject("encrypt")) and + Cipher.isWeak() and + Cipher.getName() = name + ) + } + + override string toString() { + result = "Use of weak crypto algorithm " + name + } + + } + +} + +module Cryptography { + + PackageObject ciphers() { + result.getName() = "cryptography.hazmat.primitives.ciphers" + } + + class CipherClass extends ClassObject { + CipherClass() { + ciphers().getAttribute("Cipher") = this + } + + } + + class AlgorithmClass extends ClassObject { + + AlgorithmClass() { + ciphers().submodule("algorithms").getAttribute(_) = this + } + + string getAlgorithmName() { + result = this.declaredAttribute("name").(StringObject).getText() + } + + predicate isWeak() { + exists(CryptoLib::CryptographicAlgorithm algo | + algo.getName() = this.getAlgorithmName() and + algo.isWeak() + ) + } + } + + class CipherInstance extends TaintKind { + + AlgorithmClass cls; + + CipherInstance() { + this = "cryptography.Cipher." + cls.getAlgorithmName() + } + + AlgorithmClass getAlgorithm() { + result = cls + } + + predicate isWeak() { + cls.isWeak() + } + + override TaintKind getTaintOfMethodResult(string name) { + name = "encryptor" and + result.(Encryptor).getAlgorithm() = this.getAlgorithm() + } + + } + + class CipherSource extends TaintSource { + + CipherSource() { + this.(CallNode).getFunction().refersTo(any(CipherClass cls)) + } + + override predicate isSourceOf(TaintKind kind) { + this.(CallNode).getArg(0).refersTo(_, kind.(CipherInstance).getAlgorithm(), _) + } + + override string toString() { + result = "cryptography.Cipher.source" + } + + } + + class Encryptor extends TaintKind { + + AlgorithmClass cls; + + Encryptor() { + this = "cryptography.encryptor." + cls.getAlgorithmName() + + } + + AlgorithmClass getAlgorithm() { + result = cls + } + + } + + class CryptographyWeakCryptoSink extends WeakCryptoSink { + + CryptographyWeakCryptoSink() { + exists(CallNode call, AttrNode method, Encryptor encryptor | + call.getAnArg() = this and + call.getFunction() = method and + encryptor.taints(method.getObject("update")) and + encryptor.getAlgorithm().isWeak() + ) + } + + override string toString() { + result = "Use of weak crypto algorithm" + } + + } + + +} + + diff --git a/python/ql/src/semmle/python/security/Exceptions.qll b/python/ql/src/semmle/python/security/Exceptions.qll new file mode 100644 index 000000000000..d0ecee350d58 --- /dev/null +++ b/python/ql/src/semmle/python/security/Exceptions.qll @@ -0,0 +1,136 @@ +/** + * Provides classes and predicates for tracking exceptions and information + * associated with exceptions. + */ + +import python +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Basic + +private ModuleObject theTracebackModule() { + result.getName() = "traceback" +} + +private FunctionObject traceback_function(string name) { + result = theTracebackModule().getAttribute(name) +} + +/** + * This represents information relating to an exception, for instance the + * message, arguments or parts of the exception traceback. + */ +class ExceptionInfo extends StringKind { + + ExceptionInfo() { + this = "exception.info" + } + + override string repr() { + result = "exception info" + } + +} + + +/** + * This kind represents exceptions themselves. + */ +class ExceptionKind extends TaintKind { + + ExceptionKind() { + this = "exception.kind" + } + + override string repr() { + result = "exception" + } + + override TaintKind getTaintOfAttribute(string name) { + name = "args" and result instanceof ExceptionInfoSequence + or + name = "message" and result instanceof ExceptionInfo + } +} + +/** + * A source of exception objects, either explicitly created, or captured by an + * `except` statement. + */ +class ExceptionSource extends TaintSource { + + ExceptionSource() { + exists(ClassObject cls | + cls.isSubclassOf(theExceptionType()) and + this.(ControlFlowNode).refersTo(_, cls, _) + ) + or + this = any(ExceptStmt s).getName().getAFlowNode() + } + + override string toString() { + result = "exception.source" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExceptionKind + } +} + +/** + * Represents a sequence of pieces of information relating to an exception, + * for instance the contents of the `args` attribute, or the stack trace. + */ +class ExceptionInfoSequence extends SequenceKind { + ExceptionInfoSequence() { + this.getItem() instanceof ExceptionInfo + } +} + + +/** + * Represents calls to functions in the `traceback` module that return + * sequences of exception information. + */ +class CallToTracebackFunction extends TaintSource { + + CallToTracebackFunction() { + exists(string name | + name = "extract_tb" or + name = "extract_stack" or + name = "format_list" or + name = "format_exception_only" or + name = "format_exception" or + name = "format_tb" or + name = "format_stack" + | + this = traceback_function(name).getACall() + ) + } + + override string toString() { + result = "exception.info.sequence.source" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExceptionInfoSequence + } +} + +/** + * Represents calls to functions in the `traceback` module that return a single + * string of information about an exception. + */ +class FormattedTracebackSource extends TaintSource { + + FormattedTracebackSource() { + this = traceback_function("format_exc").getACall() + } + + override string toString() { + result = "exception.info.source" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExceptionInfo + } +} diff --git a/python/ql/src/semmle/python/security/Paths.qll b/python/ql/src/semmle/python/security/Paths.qll new file mode 100644 index 000000000000..93ba6ea3b04f --- /dev/null +++ b/python/ql/src/semmle/python/security/Paths.qll @@ -0,0 +1,25 @@ +import python + +import semmle.python.security.TaintTracking + +query predicate edges(TaintedNode fromnode, TaintedNode tonode) { + fromnode.getASuccessor() = tonode +} + +private TaintedNode first_child(TaintedNode parent) { + result.getContext().getCaller() = parent.getContext() and + parent.getASuccessor() = result +} + +private TaintedNode next_sibling(TaintedNode child) { + child.getASuccessor() = result and + child.getContext() = result.getContext() +} + +query predicate parents(TaintedNode child, TaintedNode parent) { + child = first_child(parent) or + exists(TaintedNode prev | + parents(prev, parent) and + child = next_sibling(prev) + ) +} diff --git a/python/ql/src/semmle/python/security/README.md b/python/ql/src/semmle/python/security/README.md new file mode 100644 index 000000000000..1b8af176c2f3 --- /dev/null +++ b/python/ql/src/semmle/python/security/README.md @@ -0,0 +1,95 @@ +# Python Taint Tracking Library + +The taint tracking library can be broken down into three parts. + +1. Specification of sources, sinks and flows. +2. The high level query API +3. The implementation. + + +## Specification + +There are five parts to the specification of a taint tracking query. +These are: + +1. Kinds + + The Python taint tracking library supports arbitrary kinds of taint. This is useful where you want to track something related to "taint", but that is in itself not dangerous. +For example, we might want to track the flow of requests objects. Request objects are not in themselves tainted, but they do contain tainted data. For example, the length or timestamp of a request may not pose a risk, but the GET or POST string probably do. +So, we would want to track request objects distinctly from the request data in the GET or POST field. + +2. Sources + + Sources of taint can be added by importing a predefined sub-type of `TaintSource`, or defining new ones. + +3. Sinks (or vulnerabilities) + + Sinks can be add by importing a predefined sub-type of `TaintSink` or defining new ones. + +4. Data flow extensions + + Additional dataflow edges; node->node, node->var, var->var or var->node can be added by importing predefined extensions or by adding new ones. Additional edges can be specified by overriding `DataFlowExtension::DataFlowNode` or `DataFlowExtension::DataFlowVariable`. + +5. Taint tracking extensions + + Taint tracking extensions, where a only a particular kind of taint flows, can be added by overriding any or all of following the methods on `TaintKind`: + + The two general purpose extensions: + + `predicate additionalTaintStep(ControlFlowNode fromnode, ControlFlowNode tonode)` + + `predicate additionalTaintStepVar(EssaVariable fromvar, EssaVariable var)` + + And the two special purpose extensions for tainted methods or attributes. These allow simple taint-tracking extensions, without worrying about the underlying flow graph. + + `TaintKind getTaintFromAttribute(string name)` + + `TaintKind getTaintFromMethod(string name)` + + +## The high-level query API + +The `TaintedNode` fully describes the taint flow graph. +The full graph can be expressed as: + +```ql +from TaintedNode n, TaintedNode s +where s = n.getASuccessor() +select n, s +``` + +The source -> sink relation can be expressed either using `TaintedNode`: +```ql +from TaintedNode src, TaintedNode sink +where src.isSource() and sink.isSink() and src.getASuccessor*() = sink +select src, sink +``` +or, using the specification API: +```ql +from TaintSource src, TaintSink sink +where src.flowsToSink(sink) +select src, sink +``` + +## The implementation + +The data-flow graph used by the taint-tracking library is the one created by the points-to analysis, +and consists of the course data-flow graph produced by `semmle/python/data-flow/SsaDefinitions.qll` +enhanced with precise variable flows, call graph and type information. +This graph is then enhanced with additional flows specified in part 1 above. +Since the call graph and points-to information is context sensitive, the taint graph must also be context sensitive. + +The taint graph is a simple directed graph where each node consists of a +`(CFG node, context, taint)` triple although it could be thought of more naturally +as a number of distinct graphs, one for each input taint-kind consisting of data flow nodes, +`(CFG node, context)` pairs, labelled with their `taint`. + +The `TrackedValue` used in the implementation is not the taint kind specified by the user, +but describes both the kind of taint and how that taint relates to any object referred to by a data-flow graph node or edge. +Currently, only two types of `taint` are supported: simple taint, where the object is actually tainted; +and attribute taint where a named attribute of the referred object is tainted. + +Support for tainted members (both specific members of tuples and the like, +and generic members for mutable collections) are likely to be added in the near future and others form are possible. +The types of taints are hard-wired with no user-visible extension method at the moment. + diff --git a/python/ql/src/semmle/python/security/SensitiveData.qll b/python/ql/src/semmle/python/security/SensitiveData.qll new file mode 100644 index 000000000000..6786c2498f5c --- /dev/null +++ b/python/ql/src/semmle/python/security/SensitiveData.qll @@ -0,0 +1,103 @@ +/** + * Provides classes and predicates for identifying sensitive data and methods for security. + * + * 'Sensitive' data in general is anything that should not be sent around in unencrypted form. This + * library tries to guess where sensitive data may either be stored in a variable or produced by a + * method. + * + * In addition, there are methods that ought not to be executed or not in a fashion that the user + * can control. This includes authorization methods such as logins, and sending of data, etc. + */ + +import python +import semmle.python.security.TaintTracking + + +/** A regular expression that identifies strings that look like they represent secret data that are not passwords. */ +private string suspiciousNonPassword() { + result = "(?is).*(account|accnt|(? sink relation can be expressed either using `TaintedNode`: + * ```ql + * from TaintedNode src, TaintedNode sink + * where src.isSource() and sink.isSink() and src.getASuccessor*() = sink + * select src, sink + * ``` + * or, using the specification API: + * ```ql + * from TaintSource src, TaintSink sink + * where src.flowsToSink(sink) + * select src, sink + * ``` + * + * ## The implementation + * + * The data-flow graph used by the taint-tracking library is the one created by the points-to analysis, + * and consists of the base data-flow graph produced by `semmle/python/data-flow/SsaDefinitions.qll` + * enhanced with precise variable flows, call graph and type information. + * This graph is then enhanced with additional flows as specified above. + * Since the call graph and points-to information is context sensitive, the taint graph must also be context sensitive. + * + * The taint graph is a directed graph where each node consists of a + * `(CFG node, context, taint)` triple although it could be thought of more naturally + * as a number of distinct graphs, one for each input taint-kind consisting of data flow nodes, + * `(CFG node, context)` pairs, labelled with their `taint`. + * + * The `TrackedValue` used in the implementation is not the taint kind specified by the user, + * but describes both the kind of taint and how that taint relates to any object referred to by a data-flow graph node or edge. + * Currently, only two types of `taint` are supported: simple taint, where the object is actually tainted; + * and attribute taint where a named attribute of the referred object is tainted. + * + * Support for tainted members (both specific members of tuples and the like, + * and generic members for mutable collections) are likely to be added in the near future and other forms are possible. + * The types of taints are hard-wired with no user-visible extension method at the moment. + */ + +import python +private import semmle.python.pointsto.Filters as Filters + +/** A 'kind' of taint. This may be almost anything, + * but it is typically something like a "user-defined string". + * Examples include, data from a http request object, + * data from an SMS or other mobile data source, + * or, for a super secure system, environment variables or + * the local file system. + */ +abstract class TaintKind extends string { + + bindingset[this] + TaintKind() { any() } + + /** Gets the kind of taint that the named attribute will have if an object is tainted with this taint. + * In other words, if `x` has this kind of taint then it implies that `x.name` + * has `result` kind of taint. + */ + TaintKind getTaintOfAttribute(string name) { none() } + + /** Gets the kind of taint results from calling the named method if an object is tainted with this taint. + * In other words, if `x` has this kind of taint then it implies that `x.name()` + * has `result` kind of taint. + */ + TaintKind getTaintOfMethodResult(string name) { none() } + + /** Gets the taint resulting from the flow step `fromnode` -> `tonode`. + */ + TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { none() } + + /** DEPRECATED -- Use `TaintFlow.additionalFlowStepVar(EssaVariable fromvar, EssaVariable tovar, TaintKind kind)` instead. + * + * Holds if this kind of taint passes from variable `fromvar` to variable `tovar` + * This predicate is present for completeness. It is unlikely that any `TaintKind` + * implementation will ever need to override it. + */ + predicate additionalFlowStepVar(EssaVariable fromvar, EssaVariable tovar) { none() } + + /** Holds if this kind of taint can start from `expr`. + * In other words, is `expr` a source of this kind of taint. + */ + final predicate startsFrom(ControlFlowNode expr) { + expr.(TaintSource).isSourceOf(this, _) + } + + /** Holds if this kind of taint "taints" `expr`. + */ + final predicate taints(ControlFlowNode expr) { + exists(TaintedNode n | + n.getTaintKind() = this and n.getNode() = expr + ) + } + + /** Gets the class of this kind of taint. + * For example, if this were a kind of string taint + * the `result` would be `theStrType()`. + */ + ClassObject getClass() { + none() + } + + string repr() { result = this } + +} + +/** Taint kinds representing collections of other taint kind. + * We use `{kind}` to represent a mapping of string to `kind` and + * `[kind]` to represent a flat collection of `kind`. + * The use of `{` and `[` is chosen to reflect dict and list literals + * in Python. We choose a single character prefix and suffix for simplicity + * and ease of preventing infinite recursion. + */ +abstract class CollectionKind extends TaintKind { + + bindingset[this] + CollectionKind() { + (this.charAt(0) = "[" or this.charAt(0) = "{") and + /* Prevent any collection kinds more than 2 deep */ + not this.charAt(2) = "[" and not this.charAt(2) = "{" + } +} + +/** A taint kind representing a flat collections of kinds. + * Typically a sequence, but can include sets. + */ +class SequenceKind extends CollectionKind { + + TaintKind itemKind; + + SequenceKind() { + this = "[" + itemKind + "]" + } + + TaintKind getItem() { + result = itemKind + } + + override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { + sequence_subscript_taint(tonode, fromnode, this, result) + or + result = this and + ( + slice(fromnode, tonode) or + tonode.(BinaryExprNode).getAnOperand() = fromnode + ) + or + result = this and copy_call(fromnode, tonode) + or + exists(BinaryExprNode mod | + mod = tonode and + mod.getOp() instanceof Mod and + mod.getAnOperand() = fromnode and + result = this.getItem() and + result.getClass() = theStrType() + ) + or + result = this and sequence_call(fromnode, tonode) + } + + override TaintKind getTaintOfMethodResult(string name) { + name = "pop" and result = this.getItem() + } + + override string repr() { + result = "sequence of " + itemKind + } + +} + +/* Helper for getTaintForStep() */ +pragma [noinline] +private predicate sequence_subscript_taint(SubscriptNode sub, ControlFlowNode obj, SequenceKind seq, TaintKind key) { + sub.isLoad() and + sub.getValue() = obj and + if sub.getNode().getIndex() instanceof Slice then + seq = key + else + key = seq.getItem() +} + +/* tonode = fromnode[:] */ +private predicate slice(ControlFlowNode fromnode, SubscriptNode tonode) { + exists(Slice all | + all = tonode.getIndex().getNode() and + not exists(all.getStart()) and not exists(all.getStop()) and + tonode.getValue() = fromnode + ) +} + +/* A call that returns a copy (or similar) of the argument */ +private predicate copy_call(ControlFlowNode fromnode, CallNode tonode) { + tonode.getFunction().(AttrNode).getObject("copy") = fromnode + or + exists(ModuleObject copy, string name | + name = "copy" or name = "deepcopy" | + copy.getAttribute(name).(FunctionObject).getACall() = tonode and + tonode.getArg(0) = fromnode + ) + or + tonode.getFunction().refersTo(builtin_object("reversed")) and + tonode.getArg(0) = fromnode +} + +/** A taint kind representing a mapping of objects to kinds. + * Typically a dict, but can include other mappings. + */ +class DictKind extends CollectionKind { + + TaintKind valueKind; + + DictKind() { + this = "{" + valueKind + "}" + } + + TaintKind getValue() { + result = valueKind + } + + override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { + result = valueKind and + tonode.(SubscriptNode).getValue() = fromnode and tonode.isLoad() + or + result = valueKind and + tonode.(CallNode).getFunction().(AttrNode).getObject("get") = fromnode + or + result = this and copy_call(fromnode, tonode) + or + result = this and + tonode.(CallNode).getFunction().refersTo(theDictType()) and + tonode.(CallNode).getArg(0) = fromnode + } + + override TaintKind getTaintOfMethodResult(string name) { + name = "get" and result = valueKind + or + name = "values" and result.(SequenceKind).getItem() = valueKind + or + name = "itervalues" and result.(SequenceKind).getItem() = valueKind + } + + override string repr() { + result = "dict of " + valueKind + } + +} + + +/** A type of sanitizer of untrusted data. + * Examples include sanitizers for http responses, for DB access or for shell commands. + * Usually a sanitizer can only sanitize data for one particular use. + * For example, a sanitizer for DB commands would not be safe to use for http responses. + */ +abstract class Sanitizer extends string { + + bindingset[this] + Sanitizer() { any() } + + /** Holds if `taint` cannot flow through `node`. */ + predicate sanitizingNode(TaintKind taint, ControlFlowNode node) { none() } + + /** Holds if `call` removes removes the `taint` */ + predicate sanitizingCall(TaintKind taint, FunctionObject callee) { none() } + + /** Holds if `test` shows value to be untainted with `taint` */ + predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { none() } + + /** Holds if `test` shows value to be untainted with `taint` */ + predicate sanitizingSingleEdge(TaintKind taint, SingleSuccessorGuard test) { none() } + + /** Holds if `def` shows value to be untainted with `taint` */ + predicate sanitizingDefinition(TaintKind taint, EssaDefinition def) { none() } + +} + +/** DEPRECATED -- Use DataFlowExtension instead. + * An extension to taint-flow. For adding library or framework specific flows. + * Examples include flow from a request to untrusted part of that request or + * from a socket to data from that socket. + */ +abstract class TaintFlow extends string { + + bindingset[this] + TaintFlow() { any() } + + /** Holds if `fromnode` being tainted with `fromkind` will result in `tonode` being tainted with `tokind`. + * Extensions to `TaintFlow` should override this to provide additional taint steps. + */ + predicate additionalFlowStep(ControlFlowNode fromnode, TaintKind fromkind, ControlFlowNode tonode, TaintKind tokind) { none() } + + /** Holds if the given `kind` of taint passes from variable `fromvar` to variable `tovar`. + * This predicate is present for completeness. Most `TaintFlow` implementations will not need to override it. + */ + predicate additionalFlowStepVar(EssaVariable fromvar, EssaVariable tovar, TaintKind kind) { none() } + + /** Holds if the given `kind` of taint cannot pass from variable `fromvar` to variable `tovar`. + * This predicate is present for completeness. Most `TaintFlow` implementations will not need to override it. + */ + predicate prunedFlowStepVar(EssaVariable fromvar, EssaVariable tovar, TaintKind kind) { none() } + +} + +/** A source of taintedness. + * Users of the taint tracking library should override this + * class to provide their own sources. + */ +abstract class TaintSource extends @py_flow_node { + + string toString() { result = "Taint source" } + + /** + * Holds if `this` is a source of taint kind `kind` + * + * This must be overridden by subclasses to specify sources of taint. + * + * The smaller this predicate is, the faster `Taint.flowsTo()` will converge. + */ + abstract predicate isSourceOf(TaintKind kind); + + /** + * Holds if `this` is a source of taint kind `kind` for the given context. + * Generally, this should not need to be overridden; overriding `isSourceOf(kind)` should be sufficient. + * + * The smaller this predicate is, the faster `Taint.flowsTo()` will converge. + */ + predicate isSourceOf(TaintKind kind, CallContext context) { + context.appliesTo(this) and this.isSourceOf(kind) + } + + Location getLocation() { + result = this.(ControlFlowNode).getLocation() + } + + predicate hasLocationInfo(string fp, int bl, int bc, int el, int ec) { + this.getLocation().hasLocationInfo(fp, bl, bc, el, ec) + } + + /** Gets a TaintedNode for this taint source */ + TaintedNode getATaintNode() { + exists(TaintFlowImplementation::TrackedTaint taint, CallContext context | + this.isSourceOf(taint.getKind(), context) and + result = TTaintedNode_(taint, context, this) + ) + } + + /** Holds if taint can flow from this source to sink `sink` */ + final predicate flowsToSink(TaintKind srckind, TaintSink sink) { + exists(TaintedNode t | + t = this.getATaintNode() and + t.getTaintKind() = srckind and + t.flowsToSink(sink) + ) + } + + /** Holds if taint can flow from this source to taint sink `sink` */ + final predicate flowsToSink(TaintSink sink) { + this.flowsToSink(_, sink) + or + this instanceof ValidatingTaintSource and + sink instanceof ValidatingTaintSink and + exists(error()) + } +} + + +/** Warning: Advanced feature. Users are strongly recommended to use `TaintSource` instead. + * A source of taintedness on the ESSA data-flow graph. + * Users of the taint tracking library can override this + * class to provide their own sources on the ESSA graph. + */ +abstract class TaintedDefinition extends EssaNode { + + /** + * Holds if `this` is a source of taint kind `kind` + * + * This should be overridden by subclasses to specify sources of taint. + * + * The smaller this predicate is, the faster `Taint.flowsTo()` will converge. + */ + abstract predicate isSourceOf(TaintKind kind); + + /** + * Holds if `this` is a source of taint kind `kind` for the given context. + * Generally, this should not need to be overridden; overriding `isSourceOf(kind)` should be sufficient. + * + * The smaller this predicate is, the faster `Taint.flowsTo()` will converge. + */ + predicate isSourceOf(TaintKind kind, CallContext context) { + context.appliesToScope(this.getScope()) and this.isSourceOf(kind) + } + +} + +private class DictUpdate extends DataFlowExtension::DataFlowNode { + + MethodCallsiteRefinement call; + + DictUpdate() { + exists(CallNode c | + c = call.getCall() + | + c.getFunction().(AttrNode).getName() = "update" and + c.getArg(0) = this + ) + } + + override EssaVariable getASuccessorVariable() { + call.getVariable() = result + } + +} + +private class SequenceExtends extends DataFlowExtension::DataFlowNode { + + MethodCallsiteRefinement call; + + SequenceExtends() { + exists(CallNode c | + c = call.getCall() + | + c.getFunction().(AttrNode).getName() = "extend" and + c.getArg(0) = this + ) + } + + override EssaVariable getASuccessorVariable() { + call.getVariable() = result + } + +} + +/** A node that is vulnerable to one or more types of taint. + * These nodes provide the sinks when computing the taint flow graph. + * An example would be an argument to a write to a http response object, + * such an argument would be vulnerable to unsanitized user-input (XSS). + * + * Users of the taint tracking library should extend this + * class to provide their own sink nodes. + */ +abstract class TaintSink extends @py_flow_node { + + string toString() { result = "Taint sink" } + + /** + * Holds if `this` "sinks" taint kind `kind` + * Typically this means that `this` is vulnerable to taint kind `kind`. + * + * This must be overridden by subclasses to specify vulnerabilities or other sinks of taint. + */ + abstract predicate sinks(TaintKind taint); + + Location getLocation() { + result = this.(ControlFlowNode).getLocation() + } + + predicate hasLocationInfo(string fp, int bl, int bc, int el, int ec) { + this.getLocation().hasLocationInfo(fp, bl, bc, el, ec) + } + +} + +/** Extension for data-flow, to help express data-flow paths that are + * library or framework specific and cannot be inferred by the general + * data-flow machinery. + */ +module DataFlowExtension { + + /** A control flow node that modifies the basic data-flow. */ + abstract class DataFlowNode extends @py_flow_node { + + string toString() { + result = "Dataflow extension node" + } + + /** Gets a successor node for data-flow. + * Data (all forms) is assumed to flow from `this` to `result` + */ + ControlFlowNode getASuccessorNode() { none() } + + /** Gets a successor variable for data-flow. + * Data (all forms) is assumed to flow from `this` to `result`. + * Note: This is an unlikely form of flow. See `DataFlowVariable.getASuccessorVariable()` + */ + EssaVariable getASuccessorVariable() { none() } + + /** Holds if data cannot flow from `this` to `succ`, + * even though it would normally do so. + */ + predicate prunedSuccessor(ControlFlowNode succ) { none() } + + /** Gets a successor node, where the successor node will be tainted with `tokind` + * when `this` is tainted with `fromkind`. + * Extensions to `DataFlowNode` should override this to provide additional taint steps. + */ + ControlFlowNode getASuccessorNode(TaintKind fromkind, TaintKind tokind) { none() } + + /** Gets a successor node for data-flow with a change of context from callee to caller + * (going *up* the call-stack) across call-site `call`. + * Data (all forms) is assumed to flow from `this` to `result` + * Extensions to `DataFlowNode` should override this to provide additional taint steps. + */ + ControlFlowNode getAReturnSuccessorNode(CallNode call) { none() } + + /** Gets a successor node for data-flow with a change of context from caller to callee + * (going *down* the call-stack) across call-site `call`. + * Data (all forms) is assumed to flow from `this` to `result` + * Extensions to `DataFlowNode` should override this to provide additional taint steps. + */ + ControlFlowNode getACalleeSuccessorNode(CallNode call) { none() } + + } + + /** Data flow variable that modifies the basic data-flow. */ + class DataFlowVariable extends EssaVariable { + + /** Gets a successor node for data-flow. + * Data (all forms) is assumed to flow from `this` to `result` + * Note: This is an unlikely form of flow. See `DataFlowNode.getASuccessorNode()` + */ + ControlFlowNode getASuccessorNode() { none() } + + /** Gets a successor variable for data-flow. + * Data (all forms) is assumed to flow from `this` to `result`. + */ + EssaVariable getASuccessorVariable() { none() } + + /** Holds if data cannot flow from `this` to `succ`, + * even though it would normally do so. + */ + predicate prunedSuccessor(EssaVariable succ) { none() } + + } +} + +private newtype TTaintedNode = + TTaintedNode_(TaintFlowImplementation::TrackedValue taint, CallContext context, ControlFlowNode n) { + exists(TaintKind kind | + taint = TaintFlowImplementation::TTrackedTaint(kind) | + n.(TaintSource).isSourceOf(kind, context) + ) + or + TaintFlowImplementation::step(_, taint, context, n) and + exists(TaintKind kind | + kind = taint.(TaintFlowImplementation::TrackedTaint).getKind() + or + kind = taint.(TaintFlowImplementation::TrackedAttribute).getKind(_) | + not exists(Sanitizer sanitizer | + sanitizer.sanitizingNode(kind, n) + ) + ) + or + user_tainted_def(_, taint, context, n) + } + +private predicate user_tainted_def(TaintedDefinition def, TaintFlowImplementation::TTrackedTaint taint, CallContext context, ControlFlowNode n) { + exists(TaintKind kind | + taint = TaintFlowImplementation::TTrackedTaint(kind) and + def.isSourceOf(kind, context) and + n = def.getDefiningNode() + ) +} + +/** A tainted data flow graph node. + * This is a triple of `(CFG node, data-flow context, taint)` + */ +class TaintedNode extends TTaintedNode { + + string toString() { result = this.getTrackedValue().repr() } + + string debug() { result = this.getTrackedValue().toString() + " at " + this.getNode().getLocation() } + + TaintedNode getASuccessor() { + exists(TaintFlowImplementation::TrackedValue tokind, CallContext tocontext, ControlFlowNode tonode | + result = TTaintedNode_(tokind, tocontext, tonode) and + TaintFlowImplementation::step(this, tokind, tocontext, tonode) + ) + } + + /** Gets the taint for this node. */ + TaintFlowImplementation::TrackedValue getTrackedValue() { + this = TTaintedNode_(result, _, _) + } + + /** Gets the CFG node for this node. */ + ControlFlowNode getNode() { + this = TTaintedNode_(_, _, result) + } + + /** Gets the data-flow context for this node. */ + CallContext getContext() { + this = TTaintedNode_(_, result, _) + } + + Location getLocation() { + result = this.getNode().getLocation() + } + + /** Holds if this node is a source of taint */ + predicate isSource() { + exists(TaintFlowImplementation::TrackedTaint taint, CallContext context, TaintSource node | + this = TTaintedNode_(taint, context, node) and + node.isSourceOf(taint.getKind(), context) + ) + } + + /** Gets the kind of taint that node is tainted with. + * Doesn't apply if an attribute or item is tainted, only if this node directly tainted + * */ + TaintKind getTaintKind() { + this.getTrackedValue().(TaintFlowImplementation::TrackedTaint).getKind() = result + } + + /** Holds if taint flows from this node to the sink `sink` and + * reaches with a taint that `sink` is a sink of. + */ + predicate flowsToSink(TaintSink sink) { + exists(TaintedNode node | + this.getASuccessor*() = node and + node.getNode() = sink and + sink.sinks(node.getTaintKind()) + ) + } + + /** Holds if the underlying CFG node for this node is a vulnerable node + * and is vulnerable to this node's taint. + */ + predicate isVulnerableSink() { + exists(TaintedNode src, TaintSink vuln | + src.isSource() and + src.getASuccessor*() = this and + vuln = this.getNode() and + vuln.sinks(this.getTaintKind()) + ) + } + + TaintFlowImplementation::TrackedTaint fromAttribute(string name) { + result = this.getTrackedValue().(TaintFlowImplementation::TrackedAttribute).fromAttribute(name) + } + +} + +class TaintedPathSource extends TaintedNode { + + TaintedPathSource() { + this.getNode().(TaintSource).isSourceOf(this.getTaintKind(), this.getContext()) + } + + /** Holds if taint can flow from this source to sink `sink` */ + final predicate flowsTo(TaintedPathSink sink) { + this.getASuccessor*() = sink + } + + TaintSource getSource() { + result = this.getNode() + } + +} + +class TaintedPathSink extends TaintedNode { + + TaintedPathSink() { + this.getNode().(TaintSink).sinks(this.getTaintKind()) + } + + TaintSink getSink() { + result = this.getNode() + } + +} + +/** This module contains the implementation of taint-flow. + * It is recommended that users use the `TaintedNode` class, rather than using this module directly + * as the interface of this module may change without warning. + */ +library module TaintFlowImplementation { + + import semmle.python.pointsto.PointsTo + import DataFlowExtension + + newtype TTrackedValue = + TTrackedTaint(TaintKind kind) + or + TTrackedAttribute(string name, TaintKind kind) { + exists(AttributeAssignment def, TaintedNode origin | + def.getName() = name and + def.getValue() = origin.getNode() and + origin.getTaintKind() = kind + ) + or + exists(TaintedNode origin | + import_flow(origin, _, _, name) and + origin.getTaintKind() = kind + ) + or + exists(TaintKind src | + kind = src.getTaintOfAttribute(name) + ) + or + exists(TaintedNode origin, AttrNode lhs, ControlFlowNode rhs | + lhs.getName() = name and rhs = lhs.(DefinitionNode).getValue() | + origin.getNode() = rhs and + kind = origin.getTaintKind() + ) + } + + /** The "taint" tracked internal by the TaintFlow module. + * This is not the taint kind specified by the user, but describes both the kind of taint + * and how that taint relates to any object referred to by a data-flow graph node or edge. + */ + class TrackedValue extends TTrackedValue { + + abstract string toString(); + + abstract string repr(); + + abstract TrackedValue toKind(TaintKind kind); + + } + + class TrackedTaint extends TrackedValue, TTrackedTaint { + + override string repr() { + result = this.getKind().repr() + } + + override string toString() { + result = "Taint " + this.getKind() + } + + TaintKind getKind() { + this = TTrackedTaint(result) + } + + override TrackedValue toKind(TaintKind kind) { + result = TTrackedTaint(kind) + } + + } + + class TrackedAttribute extends TrackedValue, TTrackedAttribute { + + override string repr() { + exists(string name, TaintKind kind | + this = TTrackedAttribute(name, kind) and + result = "." + name + "=" + kind.repr() + ) + } + + override string toString() { + exists(string name, TaintKind kind | + this = TTrackedAttribute(name, kind) and + result = "Attribute '" + name + "' taint " + kind + ) + } + + TaintKind getKind(string name) { + this = TTrackedAttribute(name, result) + } + + TrackedValue fromAttribute(string name) { + exists(TaintKind kind | + this = TTrackedAttribute(name, kind) and + result = TTrackedTaint(kind) + ) + } + + string getName() { + this = TTrackedAttribute(result, _) + } + + override TrackedValue toKind(TaintKind kind) { + result = TTrackedAttribute(this.getName(), kind) + } + + } + + predicate step(TaintedNode fromnode, TrackedValue totaint, CallContext tocontext, ControlFlowNode tonode) { + unpruned_step(fromnode, totaint, tocontext, tonode) and + tonode.getBasicBlock().likelyReachable() + } + + predicate unpruned_step(TaintedNode fromnode, TrackedValue totaint, CallContext tocontext, ControlFlowNode tonode) { + import_step(fromnode, totaint, tocontext, tonode) + or + from_import_step(fromnode, totaint, tocontext, tonode) + or + attribute_load_step(fromnode, totaint, tocontext, tonode) + or + attribute_store_step(fromnode, totaint, tocontext, tonode) + or + getattr_step(fromnode, totaint, tocontext, tonode) + or + use_step(fromnode, totaint, tocontext, tonode) + or + call_taint_step(fromnode, totaint, tocontext, tonode) + or + fromnode.getNode().(DataFlowNode).getASuccessorNode() = tonode and + fromnode.getContext() = tocontext and + totaint = fromnode.getTrackedValue() + or + exists(CallNode call | + fromnode.getNode().(DataFlowNode).getAReturnSuccessorNode(call) = tonode and + fromnode.getContext() = tocontext.getCallee(call) and + totaint = fromnode.getTrackedValue() + ) + or + exists(CallNode call | + fromnode.getNode().(DataFlowNode).getACalleeSuccessorNode(call) = tonode and + fromnode.getContext().getCallee(call) = tocontext and + totaint = fromnode.getTrackedValue() + ) + or + exists(TaintKind tokind | + fromnode.getNode().(DataFlowNode).getASuccessorNode(fromnode.getTaintKind(), tokind) = tonode and + totaint = fromnode.getTrackedValue().toKind(tokind) and + tocontext = fromnode.getContext() + ) + or + exists(TaintKind tokind | + tokind = fromnode.getTaintKind().getTaintForFlowStep(fromnode.getNode(), tonode) and + totaint = fromnode.getTrackedValue().toKind(tokind) and + tocontext = fromnode.getContext() + ) + or + exists(TaintFlow flow, TaintKind tokind | + flow.additionalFlowStep(fromnode.getNode(), fromnode.getTaintKind(), tonode, tokind) and + totaint = fromnode.getTrackedValue().toKind(tokind) and + tocontext = fromnode.getContext() + ) + or + data_flow_step(fromnode.getContext(), fromnode.getNode(), tocontext, tonode) and + totaint = fromnode.getTrackedValue() + or + exists(DataFlowVariable var | + tainted_var(var, tocontext, fromnode) and + var.getASuccessorNode() = tonode and + totaint = fromnode.getTrackedValue() + ) + or + exists(TaintKind tokind | + totaint = fromnode.getTrackedValue().toKind(tokind) and + tocontext = fromnode.getContext() + | + tokind.(DictKind).getValue() = fromnode.getTaintKind() and + dict_construct(fromnode.getNode(), tonode) + or + tokind.(SequenceKind).getItem() = fromnode.getTaintKind() and + sequence_construct(fromnode.getNode(), tonode) + ) + } + + pragma [noinline] + predicate import_step(TaintedNode fromnode, TrackedAttribute totaint, CallContext tocontext, ImportExprNode tonode) { + exists(string name | + import_flow(fromnode, tonode, tocontext, name) and + totaint.fromAttribute(name) = fromnode.getTrackedValue() + ) + } + + pragma [noinline] + private predicate import_flow(TaintedNode fromnode, ImportExprNode tonode, CallContext tocontext, string name) { + exists(ModuleObject mod | + tonode.refersTo(mod) and + module_attribute_tainted(mod, name, fromnode) and + tocontext.appliesTo(tonode) + ) + } + + pragma [noinline] + predicate data_flow_step(CallContext fromcontext, ControlFlowNode fromnode, CallContext tocontext, ControlFlowNode tonode) { + if_exp_step(fromcontext, fromnode, tocontext, tonode) + or + call_flow_step(fromcontext, fromnode, tocontext, tonode) + or + parameter_step(fromcontext, fromnode, tocontext, tonode) + } + + pragma [noinline] + predicate from_import_step(TaintedNode fromnode, TrackedValue totaint, CallContext tocontext, ControlFlowNode tonode) { + exists(string name, ImportExprNode fmod, ModuleObject mod | + fmod = tonode.(ImportMemberNode).getModule(name) and + fmod.refersTo(mod) and + tocontext.appliesTo(tonode) and + module_attribute_tainted(mod, name, fromnode) and + totaint = fromnode.getTrackedValue() + ) + } + + pragma [noinline] + predicate getattr_step(TaintedNode fromnode, TrackedValue totaint, CallContext tocontext, CallNode tonode) { + exists(ControlFlowNode arg, string name | + tonode.getFunction().refersTo(builtin_object("getattr")) and + arg = tonode.getArg(0) and + name = tonode.getArg(1).getNode().(StrConst).getText() and + arg = fromnode.getNode() and + totaint = fromnode.fromAttribute(name) and + tocontext = fromnode.getContext() + ) + } + + pragma [noinline] + predicate attribute_load_step(TaintedNode fromnode, TrackedValue totaint, CallContext tocontext, AttrNode tonode) { + tonode.isLoad() and + exists(string name, ControlFlowNode f | + f = tonode.getObject(name) and + tocontext = fromnode.getContext() and + f = fromnode.getNode() and + ( + totaint = TTrackedTaint(fromnode.getTaintKind().getTaintOfAttribute(name)) + or + totaint = fromnode.fromAttribute(name) + ) + ) + } + + pragma [noinline] + predicate attribute_store_step(TaintedNode fromnode, TrackedAttribute totaint, CallContext tocontext, ControlFlowNode tonode) { + exists(string name | + attribute_store_flow(fromnode.getNode(), tonode, name) and + totaint.fromAttribute(name) = fromnode.getTrackedValue() + ) and + tocontext = fromnode.getContext() + } + + pragma [noinline] + private predicate attribute_store_flow(ControlFlowNode fromnode, ControlFlowNode tonode, string name) { + exists(AttrNode lhs | + tonode = lhs.getObject(name) and fromnode = lhs.(DefinitionNode).getValue() + ) + } + + predicate module_attribute_tainted(ModuleObject m, string name, TaintedNode origin) { + exists(EssaVariable var, CallContext c | + var.getName() = name and + BaseFlow::reaches_exit(var) and + var.getScope() = m.getModule() and + tainted_var(var, c, origin) and + c = TTop() + ) + } + + predicate use_step(TaintedNode fromnode, TrackedValue totaint, CallContext tocontext, ControlFlowNode tonode) { + exists(EssaVariable var | + var.getASourceUse() = tonode and + tainted_var(var, tocontext, fromnode) and + totaint = fromnode.getTrackedValue() + ) + } + + pragma [noinline] + predicate call_flow_step(CallContext callee, ControlFlowNode fromnode, CallContext caller, ControlFlowNode call) { + exists(PyFunctionObject func | + callee.appliesToScope(func.getFunction()) and + func.getACall() = call and + func.getAReturnedNode() = fromnode | + callee = caller.getCallee(call) + or + caller = callee and caller = TTop() + ) + } + + predicate call_taint_step(TaintedNode fromnode, TrackedValue totaint, CallContext tocontext, CallNode call) { + exists(string name | + call.getFunction().(AttrNode).getObject(name) = fromnode.getNode() and + totaint = TTrackedTaint(fromnode.getTaintKind().getTaintOfMethodResult(name)) and + tocontext = fromnode.getContext() + ) + or + exists(EssaVariable self, CallContext callee | + self_init_end_transfer(self, callee, call, tocontext) and + tainted_var(self, callee, fromnode) and + totaint = fromnode.getTrackedValue() + ) + } + + predicate self_init_end_transfer(EssaVariable self, CallContext callee, CallNode call, CallContext caller) { + exists(ClassObject cls, Function init | + PointsTo::instantiation(call, _, cls) and + init = cls.lookupAttribute("__init__").(FunctionObject).getFunction() and + self.getSourceVariable().(Variable).isSelf() and self.getScope() = init + | + callee = caller.getCallee(call) + or + caller = callee and caller = TTop() + ) + } + + predicate tainted_var(EssaVariable var, CallContext context, TaintedNode origin) { + tainted_def(var.getDefinition(), context, origin) + or + exists(EssaVariable prev | + tainted_var(prev, context, origin) and + prev.(DataFlowVariable).getASuccessorVariable() = var + ) + or + origin.getNode().(DataFlowNode).getASuccessorVariable() = var and + context = origin.getContext() + or + exists(TrackedTaint taint, EssaVariable prev | + tainted_var(prev, context, origin) and + origin.getTrackedValue() = taint and + taint.getKind().additionalFlowStepVar(prev, var) + ) + or + exists(TaintFlow flow, TrackedTaint taint, EssaVariable prev | + tainted_var(prev, context, origin) and + origin.getTrackedValue() = taint and + flow.additionalFlowStepVar(prev, var, taint.getKind()) + ) + } + + predicate tainted_def(EssaDefinition def, CallContext context, TaintedNode origin) { + unsanitized_tainted_def(def, context, origin) and + ( + origin.getTrackedValue() instanceof TrackedAttribute + or + exists(TaintKind kind | + kind = origin.getTaintKind() and + not exists(Sanitizer san | + san.sanitizingDefinition(kind, def) + or + san.sanitizingNode(kind, def.(EssaNode).getDefiningNode()) + ) + ) + ) + } + + predicate unsanitized_tainted_def(EssaDefinition def, CallContext context, TaintedNode origin) { + exists(TrackedValue val, ControlFlowNode node | + user_tainted_def(def, val, context, node) and + origin = TTaintedNode_(val, context, node) + ) + or + tainted_phi(def, context, origin) + or + tainted_assignment(def, context, origin) + or + tainted_attribute_assignment(def, context, origin) + or + tainted_parameter_def(def, context, origin) + or + tainted_callsite(def, context, origin) + or + tainted_method_callsite(def, context, origin) + or + tainted_edge(def, context, origin) + or + tainted_argument(def, context, origin) + or + tainted_import_star(def, context, origin) + or + tainted_uni_edge(def, context, origin) + or + tainted_scope_entry(def, context, origin) + or + tainted_with(def, context, origin) + or + tainted_exception_capture(def, context, origin) + } + + predicate tainted_scope_entry(ScopeEntryDefinition def, CallContext context, TaintedNode origin) { + exists(EssaVariable var | + BaseFlow::scope_entry_value_transfer_from_earlier(var, _, def, _) and + tainted_var(var, context, origin) + ) + } + + pragma [noinline] + predicate tainted_phi(PhiFunction phi, CallContext context, TaintedNode origin) { + exists(BasicBlock pred, EssaVariable predvar | + predvar = phi.getInput(pred) and + tainted_var(predvar, context, origin) and + not pred.unlikelySuccessor(phi.getBasicBlock()) and + not predvar.(DataFlowExtension::DataFlowVariable).prunedSuccessor(phi.getVariable()) + ) + } + + pragma [noinline] + predicate tainted_assignment(AssignmentDefinition def, CallContext context, TaintedNode origin) { + origin.getNode() = def.getValue() and + context = origin.getContext() + } + + pragma [noinline] + predicate tainted_attribute_assignment(AttributeAssignment def, CallContext context, TaintedNode origin) { + context = origin.getContext() and + origin.getNode() = def.getDefiningNode().(AttrNode).getObject() + } + + pragma [noinline] + predicate tainted_callsite(CallsiteRefinement call, CallContext context, TaintedNode origin) { + /* In the interest of simplicity and performance we assume that tainted escaping variables remain tainted across calls. + * In the cases were this assumption is false, it is easy enough to add an additional sanitizer. + */ + tainted_var(call.getInput(), context, origin) + } + + pragma [noinline] + predicate parameter_step(CallContext caller, ControlFlowNode argument, CallContext callee, NameNode param) { + exists(ParameterDefinition def | + def.getDefiningNode() = param and + exists(FunctionObject func, CallNode call | + exists(int n | argument = func.getArgumentForCall(call, n) and param.getNode() = func.getFunction().getArg(n)) + or + exists(string name | argument = func.getNamedArgumentForCall(call, name) and param.getNode() = func.getFunction().getArgByName(name)) + or + class_initializer_argument(_, _, call, func, argument, param) + | + callee = caller.getCallee(call) + ) + ) + } + + pragma [noinline] + predicate class_initializer_argument(ClassObject cls, int n, CallNode call, FunctionObject func, ControlFlowNode argument, NameNode param) { + PointsTo::instantiation(call, _, cls) and + cls.lookupAttribute("__init__") = func and + call.getArg(n) = argument and + param.getNode() = func.getFunction().getArg(n+1) + } + + pragma [noinline] + predicate tainted_parameter_def(ParameterDefinition def, CallContext context, TaintedNode fromnode) { + fromnode.getNode() = def.getDefiningNode() and + context = fromnode.getContext() + } + + pragma [noinline] + predicate if_exp_step(CallContext fromcontext, ControlFlowNode operand, CallContext tocontext, IfExprNode ifexp) { + fromcontext = tocontext and fromcontext.appliesTo(operand) and + ifexp.getAnOperand() = operand + } + + pragma [noinline] + predicate tainted_method_callsite(MethodCallsiteRefinement call, CallContext context, TaintedNode origin) { + tainted_var(call.getInput(), context, origin) and + exists(TaintKind kind | + kind = origin.getTaintKind() | + not exists(FunctionObject callee, Sanitizer sanitizer | + callee.getACall() = call.getCall() and + sanitizer.sanitizingCall(kind, callee) + ) + ) + } + + pragma [noinline] + predicate tainted_edge(PyEdgeRefinement test, CallContext context, TaintedNode origin) { + exists(EssaVariable var, TaintKind kind | + kind = origin.getTaintKind() and + var = test.getInput() and + tainted_var(var, context, origin) and + not exists(Sanitizer sanitizer | + sanitizer.sanitizingEdge(kind, test) + ) + | + not Filters::isinstance(test.getTest(), _, var.getSourceVariable().getAUse()) + or + exists(ControlFlowNode c, ClassObject cls | + Filters::isinstance(test.getTest(), c, var.getSourceVariable().getAUse()) + and c.refersTo(cls) + | + test.getSense() = true and kind.getClass().getAnImproperSuperType() = cls + or + test.getSense() = false and not kind.getClass().getAnImproperSuperType() = cls + ) + ) + } + + pragma [noinline] + predicate tainted_argument(ArgumentRefinement def, CallContext context, TaintedNode origin) { + tainted_var(def.getInput(), context, origin) + } + + pragma [noinline] + predicate tainted_import_star(ImportStarRefinement def, CallContext context, TaintedNode origin) { + exists(ModuleObject mod, string name | + PointsTo::Flow::module_and_name_for_import_star(mod, name, def, _) | + if mod.exports(name) then ( + /* Attribute from imported module */ + module_attribute_tainted(mod, name, origin) and + context.appliesTo(def.getDefiningNode()) + ) else ( + /* Retain value held before import */ + exists(EssaVariable var | + var = def.getInput() and + tainted_var(var, context, origin) + ) + ) + ) + } + + pragma [noinline] + predicate tainted_uni_edge(SingleSuccessorGuard uniphi, CallContext context, TaintedNode origin) { + exists(EssaVariable var, TaintKind kind | + kind = origin.getTaintKind() and + var = uniphi.getInput() and + tainted_var(var, context, origin) and + not exists(Sanitizer sanitizer | + sanitizer.sanitizingSingleEdge(kind, uniphi) + ) + ) + } + + pragma [noinline] + predicate tainted_with(WithDefinition def, CallContext context, TaintedNode origin) { + with_flow(_, origin.getNode(),def.getDefiningNode()) and + context = origin.getContext() + } + + pragma [noinline] + predicate tainted_exception_capture(ExceptionCapture def, CallContext context, TaintedNode fromnode) { + fromnode.getNode() = def.getDefiningNode() and + context = fromnode.getContext() + } + +} + +/* Helper predicate for tainted_with */ +private predicate with_flow(With with, ControlFlowNode contextManager, ControlFlowNode var) { + with.getContextExpr() = contextManager.getNode() and + with.getOptionalVars() = var.getNode() and + contextManager.strictlyDominates(var) +} + +/* "Magic" sources and sinks which only have `toString()`s when + * no sources are defined or no sinks are defined or no kinds are present. + * In those cases, these classes make sure that an informative error + * message is presented to the user. + */ + +library class ValidatingTaintSource extends TaintSource { + + override string toString() { + result = error() + } + + ValidatingTaintSource() { + this = uniqueCfgNode() + } + + override predicate isSourceOf(TaintKind kind) { none() } + + override predicate hasLocationInfo(string fp, int bl, int bc, int el, int ec) { + fp = error() and bl = 0 and bc = 0 and el = 0 and ec = 0 + } + + +} + +library class ValidatingTaintSink extends TaintSink { + + override string toString() { + result = error() + } + + ValidatingTaintSink() { + this = uniqueCfgNode() + } + + override predicate sinks(TaintKind kind) { none() } + + override predicate hasLocationInfo(string fp, int bl, int bc, int el, int ec) { + fp = error() and bl = 0 and bc = 0 and el = 0 and ec = 0 + } + +} + + +/* Helpers for Validating classes */ + +private string locatable_module_name() { + exists(Module m | + exists(m.getLocation()) and + result = m.getName() + ) +} + +private ControlFlowNode uniqueCfgNode() { + exists(Module m | + result = m.getEntryNode() and + m.getName() = min(string name | name = locatable_module_name()) + ) +} + +private string error() { + forall(TaintSource s | s instanceof ValidatingTaintSource) and + result = "No sources defined" + or + forall(TaintSink s | s instanceof ValidatingTaintSink) and + result = "No sinks defined" +} + + +private newtype TCallContext = + TTop() + or + TCalleeContext(CallNode call, CallContext caller, int depth) { + caller.appliesToScope(call.getScope()) and + depth = caller.getDepth() + 1 and depth < 7 and + exists(TaintedNode n | + n = TTaintedNode_(_, caller, call.getAnArg()) + ) + } + +private import semmle.python.pointsto.PointsTo + +pragma [inline] +private string shortLocation(Location l) { + result = l.getFile().getShortName() + ":" + l.getStartLine() +} + +/** Call context for use in taint-tracking. + * Using call contexts prevents "cross talk" between different calls + * to the same function. For example, if a function f is defined as + * ```python + * def f(arg): + * return arg + * ``` + * Then `f("tainted")` is "tainted", but `f("ok") is "ok". + */ +class CallContext extends TCallContext { + + string toString() { + this = TTop() and result = "" + or + exists(CallNode callsite, CallContext caller | + this = TCalleeContext(callsite, caller, _) | + result = shortLocation(callsite.getLocation()) + " from " + caller.toString() and caller = TCalleeContext(_, _, _) + or + result = shortLocation(callsite.getLocation()) and caller = TTop() + ) + } + + /** Holds if this context can apply to `n`. + */ + pragma[inline] + predicate appliesTo(ControlFlowNode n) { + this.appliesToScope(n.getScope()) + } + + /** Holds if this context can apply to `s` + */ + predicate appliesToScope(Scope s) { + this = TTop() + or + exists(FunctionObject f, CallNode call | + this = TCalleeContext(call, _, _) and + f.getFunction() = s and f.getACall() = call + ) + or + exists(ClassObject cls,CallNode call | + this = TCalleeContext(call, _, _) and + PointsTo::instantiation(call, _, cls) and + s = cls.lookupAttribute("__init__").(FunctionObject).getFunction() and + call.getFunction().refersTo(cls) + ) + } + + /** Gets the call depth of this context. + */ + int getDepth() { + this = TTop() and result = 0 + or + this = TCalleeContext(_, _, result) + } + + CallContext getCallee(CallNode call) { + result = TCalleeContext(call, this, _) + } + + CallContext getCaller() { + this = TCalleeContext(_, result, _) + } + +} + +pragma [noinline] +private predicate dict_construct(ControlFlowNode itemnode, ControlFlowNode dictnode) { + dictnode.(DictNode).getAValue() = itemnode + or + dictnode.(CallNode).getFunction().refersTo(theDictType()) and + dictnode.(CallNode).getArgByName(_) = itemnode +} + +pragma [noinline] +private predicate sequence_construct(ControlFlowNode itemnode, ControlFlowNode seqnode) { + seqnode.isLoad() and + ( + seqnode.(ListNode).getElement(_) = itemnode + or + seqnode.(TupleNode).getElement(_) = itemnode + or + seqnode.(SetNode).getAnElement() = itemnode + ) +} + + +/* A call to construct a sequence from a sequence or iterator*/ +pragma [noinline] +private predicate sequence_call(ControlFlowNode fromnode, CallNode tonode) { + tonode.getArg(0) = fromnode and + exists(ControlFlowNode cls | + cls = tonode.getFunction() | + cls.refersTo(theListType()) + or + cls.refersTo(theTupleType()) + or + cls.refersTo(theSetType()) + ) +} diff --git a/python/ql/src/semmle/python/security/flow/AnyCall.qll b/python/ql/src/semmle/python/security/flow/AnyCall.qll new file mode 100644 index 000000000000..7a766bf25a53 --- /dev/null +++ b/python/ql/src/semmle/python/security/flow/AnyCall.qll @@ -0,0 +1,15 @@ +import python +import semmle.python.security.strings.Basic + +/** Assume that taint flows from argument to result for *any* call */ +class AnyCallStringFlow extends DataFlowExtension::DataFlowNode { + + AnyCallStringFlow() { + any(CallNode call).getAnArg() = this + } + + override ControlFlowNode getASuccessorNode() { + result.(CallNode).getAnArg() = this + } + +} diff --git a/python/ql/src/semmle/python/security/injection/Command.qll b/python/ql/src/semmle/python/security/injection/Command.qll new file mode 100644 index 000000000000..fa9f7604a06c --- /dev/null +++ b/python/ql/src/semmle/python/security/injection/Command.qll @@ -0,0 +1,127 @@ +/** Provides class and predicates to track external data that + * may represent malicious OS commands. + * + * This module is intended to be imported into a taint-tracking query + * to extend `TaintKind` and `TaintSink`. + * + */ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Untrusted + + +private ModuleObject subprocessModule() { + result.getName() = "subprocess" +} + +private ModuleObject osOrPopenModule() { + result.getName() = "os" or + result.getName() = "popen2" +} + +private Object makeOsCall() { + exists(string name | + result = subprocessModule().getAttribute(name) | + name = "Popen" or + name = "call" or + name = "check_call" or + name = "check_output" or + name = "run" + ) +} + +/**Special case for first element in sequence. */ +class FirstElementKind extends TaintKind { + + FirstElementKind() { + this = "sequence[" + any(ExternalStringKind key) + "][0]" + } + + override string repr() { + result = "first item in sequence of " + this.getItem().repr() + } + + /** Gets the taint kind for item in this sequence. */ + ExternalStringKind getItem() { + this = "sequence[" + result + "][0]" + } + +} + +class FirstElementFlow extends DataFlowExtension::DataFlowNode { + + FirstElementFlow() { + this = any(SequenceNode s).getElement(0) + } + + override + ControlFlowNode getASuccessorNode(TaintKind fromkind, TaintKind tokind) { + result.(SequenceNode).getElement(0) = this and tokind.(FirstElementKind).getItem() = fromkind + } + +} + +/** A taint sink that is potentially vulnerable to malicious shell commands. + * The `vuln` in `subprocess.call(shell=vuln)` and similar calls. + */ +class ShellCommand extends TaintSink { + + override string toString() { result = "shell command" } + + ShellCommand() { + exists(CallNode call, Object istrue | + call.getFunction().refersTo(makeOsCall()) and + call.getAnArg() = this and + call.getArgByName("shell").refersTo(istrue) and + istrue.booleanValue() = true + ) + or + exists(CallNode call, string name | + call.getAnArg() = this and + call.getFunction().refersTo(osOrPopenModule().getAttribute(name)) | + name = "system" or + name = "popen" or + name.matches("popen_") + ) + or + exists(CallNode call | + call.getAnArg() = this and + call.getFunction().refersTo(any(ModuleObject commands | commands.getName() = "commands")) + ) + } + + override predicate sinks(TaintKind kind) { + /* Tainted string command */ + kind instanceof ExternalStringKind + or + /* List (or tuple) containing a tainted string command */ + kind instanceof ExternalStringSequenceKind + } + +} + +/** A taint sink that is potentially vulnerable to malicious shell commands. + * The `vuln` in `subprocess.call(vuln, ...)` and similar calls. + */ +class OsCommandFirstArgument extends TaintSink { + + override string toString() { result = "OS command first argument" } + + OsCommandFirstArgument() { + not this instanceof ShellCommand and + exists(CallNode call| + call.getFunction().refersTo(makeOsCall()) and + call.getArg(0) = this + ) + } + + override predicate sinks(TaintKind kind) { + /* Tainted string command */ + kind instanceof ExternalStringKind + or + /* List (or tuple) whose first element is tainted */ + kind instanceof FirstElementKind + } + +} diff --git a/python/ql/src/semmle/python/security/injection/Exec.qll b/python/ql/src/semmle/python/security/injection/Exec.qll new file mode 100644 index 000000000000..f16c3bfd4392 --- /dev/null +++ b/python/ql/src/semmle/python/security/injection/Exec.qll @@ -0,0 +1,42 @@ +/** Provides class and predicates to track external data that + * may represent malicious Python code. + * + * This module is intended to be imported into a taint-tracking query + * to extend `TaintKind` and `TaintSink`. + * + */ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Untrusted + + +private FunctionObject exec_or_eval() { + result = builtin_object("exec") + or + result = builtin_object("eval") +} + +/** A taint sink that represents an argument to exec or eval that is vulnerable to malicious input. + * The `vuln` in `exec(vuln)` or similar. + */ +class StringEvaluationNode extends TaintSink { + + override string toString() { result = "exec or eval" } + + StringEvaluationNode() { + exists(Exec exec | + exec.getASubExpression().getAFlowNode() = this + ) + or + exists(CallNode call | + exec_or_eval().getACall() = call and + call.getAnArg() = this + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + +} diff --git a/python/ql/src/semmle/python/security/injection/Marshal.qll b/python/ql/src/semmle/python/security/injection/Marshal.qll new file mode 100644 index 000000000000..b9b712c43daa --- /dev/null +++ b/python/ql/src/semmle/python/security/injection/Marshal.qll @@ -0,0 +1,36 @@ +/** Provides class and predicates to track external data that + * may represent malicious marshals. + * + * This module is intended to be imported into a taint-tracking query + * to extend `TaintKind` and `TaintSink`. + * + */ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Untrusted + + +private FunctionObject marshalLoads() { + result = any(ModuleObject marshal | marshal.getName() = "marshal").getAttribute("loads") +} + + +/** A taint sink that is potentially vulnerable to malicious marshaled objects. + * The `vuln` in `marshal.loads(vuln)`. */ +class UnmarshalingNode extends TaintSink { + + override string toString() { result = "unmarshaling vulnerability" } + + UnmarshalingNode() { + exists(CallNode call | + marshalLoads().getACall() = call and + call.getAnArg() = this + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + +} diff --git a/python/ql/src/semmle/python/security/injection/Path.qll b/python/ql/src/semmle/python/security/injection/Path.qll new file mode 100644 index 000000000000..cee8c12fc162 --- /dev/null +++ b/python/ql/src/semmle/python/security/injection/Path.qll @@ -0,0 +1,103 @@ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Untrusted + +/** Prevents taint flowing through ntpath.normpath() + * NormalizedPath below handles that case. + */ +private class PathSanitizer extends Sanitizer { + + PathSanitizer() { + this = "path.sanitizer" + } + + override predicate sanitizingNode(TaintKind taint, ControlFlowNode node) { + taint instanceof ExternalStringKind and + abspath_call(node, _) + } + +} + +private FunctionObject abspath() { + exists(ModuleObject os, ModuleObject os_path | + os.getName() = "os" and + os.getAttribute("path") = os_path | + os_path.getAttribute("abspath") = result + or + os_path.getAttribute("normpath") = result + ) +} + +/** A path that has been normalized, but not verified to be safe */ +class NormalizedPath extends TaintKind { + + NormalizedPath() { + this = "normalized.path.injection" + } + + override string repr() { + result = "normalized path" + } + +} + +private predicate abspath_call(CallNode call, ControlFlowNode arg) { + call.getFunction().refersTo(abspath()) and + arg = call.getArg(0) +} + + +class AbsPath extends DataFlowExtension::DataFlowNode { + + AbsPath() { + abspath_call(_, this) + } + + override + ControlFlowNode getASuccessorNode(TaintKind fromkind, TaintKind tokind) { + abspath_call(result, this) and tokind instanceof NormalizedPath and fromkind instanceof ExternalStringKind + } + +} + +class NormalizedPathSanitizer extends Sanitizer { + + NormalizedPathSanitizer() { + this = "normalized.path.sanitizer" + } + + override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { + taint instanceof NormalizedPath and + test.getTest().(CallNode).getFunction().(AttrNode).getName() = "startswith" and + test.getSense() = true + } + +} + +/** A taint sink that is vulnerable to malicious paths. + * The `vuln` in `open(vuln)` and similar. + */ +class OpenNode extends TaintSink { + + override string toString() { result = "argument to open()" } + + OpenNode() { + exists(CallNode call | + call.getFunction().refersTo(builtin_object("open")) and + call.getAnArg() = this + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + or + kind instanceof NormalizedPath + } + +} + + + + + diff --git a/python/ql/src/semmle/python/security/injection/Pickle.qll b/python/ql/src/semmle/python/security/injection/Pickle.qll new file mode 100644 index 000000000000..50914650d9f6 --- /dev/null +++ b/python/ql/src/semmle/python/security/injection/Pickle.qll @@ -0,0 +1,40 @@ +/** Provides class and predicates to track external data that + * may represent malicious pickles. + * + * This module is intended to be imported into a taint-tracking query + * to extend `TaintKind` and `TaintSink`. + * + */ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Untrusted + + +private ModuleObject pickleModule() { + result.getName() = "pickle" + or + result.getName() = "cPickle" +} + +private FunctionObject pickleLoads() { + result = pickleModule().getAttribute("loads") +} + +/** `pickle.loads(untrusted)` vulnerability. */ +class UnpicklingNode extends TaintSink { + + override string toString() { result = "unpickling untrusted data" } + + UnpicklingNode() { + exists(CallNode call | + pickleLoads().getACall() = call and + call.getAnArg() = this + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + +} diff --git a/python/ql/src/semmle/python/security/injection/Sql.qll b/python/ql/src/semmle/python/security/injection/Sql.qll new file mode 100644 index 000000000000..26bea04c6a7b --- /dev/null +++ b/python/ql/src/semmle/python/security/injection/Sql.qll @@ -0,0 +1,96 @@ +/** Provides class and predicates to track external data that + * may represent malicious SQL queries or parts of queries. + * + * This module is intended to be imported into a taint-tracking query + * to extend `TaintKind` and `TaintSink`. + * + */ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Untrusted + + +private StringObject first_part(ControlFlowNode command) { + command.(BinaryExprNode).getOp() instanceof Add and + command.(BinaryExprNode).getLeft().refersTo(result) + or + exists(CallNode call, SequenceObject seq | + call = command | + call = theStrType().lookupAttribute("join") and + call.getArg(0).refersTo(seq) and + seq.getInferredElement(0) = result + ) + or + command.(BinaryExprNode).getOp() instanceof Mod and + command.getNode().(StrConst).getLiteralObject() = result +} + +/** Holds if `command` appears to be a SQL command string of which `inject` is a part. */ +predicate probable_sql_command(ControlFlowNode command, ControlFlowNode inject) { + exists(string prefix | + inject = command.getAChild*() and + first_part(command).getText().regexpMatch(" *" + prefix + ".*") + | + prefix = "CREATE" or prefix = "SELECT" + ) +} + +/** A taint kind representing a DB cursor. + * This will be overridden to provide specific kinds of DB cursor. + */ +abstract class DbCursor extends TaintKind { + + bindingset[this] + DbCursor() { any() } + + string getExecuteMethodName() { result = "execute" } + +} + + +/** A part of a string that appears to be a SQL command and is thus + * vulnerable to malicious input. + */ +class SimpleSqlStringInjection extends TaintSink { + + override string toString() { result = "simple SQL string injection" } + + SimpleSqlStringInjection() { + probable_sql_command(_, this) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + +} + +/** A taint source representing sources of DB connections. + * This will be overridden to provide specific kinds of DB connection sources. + */ +abstract class DbConnectionSource extends TaintSource { + +} + +/** A taint sink that is vulnerable to malicious SQL queries. + * The `vuln` in `db.connection.execute(vuln)` and similar. + */ +class DbConnectionExecuteArgument extends TaintSink { + + override string toString() { result = "db.connection.execute" } + + DbConnectionExecuteArgument() { + exists(CallNode call, DbCursor cursor, string name | + cursor.taints(call.getFunction().(AttrNode).getObject(name)) and + cursor.getExecuteMethodName() = name and + call.getArg(0) = this + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } +} + + diff --git a/python/ql/src/semmle/python/security/injection/Xml.qll b/python/ql/src/semmle/python/security/injection/Xml.qll new file mode 100644 index 000000000000..0c4a8136bbbb --- /dev/null +++ b/python/ql/src/semmle/python/security/injection/Xml.qll @@ -0,0 +1,95 @@ +/** Provides class and predicates to track external data that + * may represent malicious XML objects. + * + * This module is intended to be imported into a taint-tracking query + * to extend `TaintKind` and `TaintSink`. + */ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Untrusted + + +private ModuleObject xmlElementTreeModule() { + result.getName() = "xml.etree.ElementTree" +} + +private ModuleObject xmlMiniDomModule() { + result.getName() = "xml.dom.minidom" +} + +private ModuleObject xmlPullDomModule() { + result.getName() = "xml.dom.pulldom" +} + +private ModuleObject xmlSaxModule() { + result.getName() = "xml.sax" +} + +private class ExpatParser extends TaintKind { + + ExpatParser() { this = "expat.parser" } + +} + +private FunctionObject expatCreateParseFunction() { + exists(ModuleObject expat | + expat.getName() = "xml.parsers.expat" and + result = expat.getAttribute("ParserCreate") + ) +} + +private class ExpatCreateParser extends TaintSource { + + ExpatCreateParser() { + expatCreateParseFunction().getACall() = this + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExpatParser + } + + string toString() { + result = "expat.create.parser" + } +} + +private FunctionObject xmlFromString() { + result = xmlElementTreeModule().getAttribute("fromstring") + or + result = xmlMiniDomModule().getAttribute("parseString") + or + result = xmlPullDomModule().getAttribute("parseString") + or + result = xmlSaxModule().getAttribute("parseString") +} + +/** A (potentially) malicious XML string. */ +class ExternalXmlString extends ExternalStringKind { + + ExternalXmlString() { + this = "external xml encoded object" + } + +} + +/** A call to an XML library function that is potentially vulnerable to a + * specially crafted XML string. + */ +class XmlLoadNode extends TaintSink { + + override string toString() { result = "xml.load vulnerability" } + + XmlLoadNode() { + exists(CallNode call | + call.getAnArg() = this | + xmlFromString().getACall() = call or + any(ExpatParser parser).taints(call.getFunction().(AttrNode).getObject("Parse")) + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalXmlString + } + +} diff --git a/python/ql/src/semmle/python/security/injection/Yaml.qll b/python/ql/src/semmle/python/security/injection/Yaml.qll new file mode 100644 index 000000000000..e2e1b983ea97 --- /dev/null +++ b/python/ql/src/semmle/python/security/injection/Yaml.qll @@ -0,0 +1,40 @@ +/** Provides class and predicates to track external data that + * may represent malicious yaml-encoded objects. + * + * This module is intended to be imported into a taint-tracking query + * to extend `TaintKind` and `TaintSink`. + * + */ + +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Untrusted + + +private ModuleObject yamlModule() { + result.getName() = "yaml" +} + + +private FunctionObject yamlLoad() { + result = yamlModule().getAttribute("load") +} + +/** `yaml.load(untrusted)` vulnerability. */ +class YamlLoadNode extends TaintSink { + + override string toString() { result = "yaml.load vulnerability" } + + YamlLoadNode() { + exists(CallNode call | + yamlLoad().getACall() = call and + call.getAnArg() = this + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + +} diff --git a/python/ql/src/semmle/python/security/strings/Basic.qll b/python/ql/src/semmle/python/security/strings/Basic.qll new file mode 100755 index 000000000000..cbb35668c5a9 --- /dev/null +++ b/python/ql/src/semmle/python/security/strings/Basic.qll @@ -0,0 +1,118 @@ +import python +private import Common + +import semmle.python.security.TaintTracking + +/** An extensible kind of taint representing any kind of string. + */ +abstract class StringKind extends TaintKind { + + bindingset[this] + StringKind() { + this = this + } + + override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { + result = this and + ( + str_method_call(fromnode, tonode) or + slice(fromnode, tonode) or + tonode.(BinaryExprNode).getAnOperand() = fromnode or + os_path_join(fromnode, tonode) or + str_format(fromnode, tonode) or + encode_decode(fromnode, tonode) or + to_str(fromnode, tonode) + ) + or + result = this and copy_call(fromnode, tonode) + } + + override ClassObject getClass() { + result = theStrType() or result = theUnicodeType() + } + +} + +private class StringEqualitySanitizer extends Sanitizer { + + StringEqualitySanitizer() { this = "string equality sanitizer" } + + /** The test `if untrusted == "KNOWN_VALUE":` sanitizes `untrusted` on its `true` edge. */ + override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { + taint instanceof StringKind and + exists(ControlFlowNode const, Cmpop op | + const.getNode() instanceof StrConst | + ( + test.getTest().(CompareNode).operands(const, op, _) + or + test.getTest().(CompareNode).operands(_, op, const) + ) and ( + op instanceof Eq and test.getSense() = true + or + op instanceof NotEq and test.getSense() = false + ) + ) + } + +} + +/* tonode = fromnode.xxx() where the call to xxx returns an identical or similar string */ +private predicate str_method_call(ControlFlowNode fromnode, CallNode tonode) { + exists(string method_name | + tonode.getFunction().(AttrNode).getObject(method_name) = fromnode + | + method_name = "strip" or method_name = "format" or + method_name = "lstrip" or method_name = "rstrip" or + method_name = "ljust" or method_name = "rjust" or + method_name = "title" or method_name = "capitalize" + ) +} + +/* tonode = ....format(fromnode) */ +private predicate str_format(ControlFlowNode fromnode, CallNode tonode) { + tonode.getFunction().(AttrNode).getName() = "format" and + ( + tonode.getAnArg() = fromnode + or + tonode.getNode().getAKeyword().getValue() = fromnode.getNode() + ) +} + +/* tonode = codec.[en|de]code(fromnode)*/ +private predicate encode_decode(ControlFlowNode fromnode, CallNode tonode) { + exists(FunctionObject func, string name | + func.getACall() = tonode and + tonode.getAnArg() = fromnode and + func.getName() = name | + name = "encode" or name = "decode" or + name = "decodestring" + ) +} + +/* tonode = str(fromnode)*/ +private predicate to_str(ControlFlowNode fromnode, CallNode tonode) { + tonode.getAnArg() = fromnode and + exists(ClassObject str | + tonode.getFunction().refersTo(str) | + str = theUnicodeType() or str = theBytesType() + ) +} + +/* tonode = fromnode[:] */ +private predicate slice(ControlFlowNode fromnode, SubscriptNode tonode) { + exists(Slice all | + all = tonode.getIndex().getNode() and + not exists(all.getStart()) and not exists(all.getStop()) and + tonode.getValue() = fromnode + ) +} + +/* tonode = os.path.join(..., fromnode, ...) */ +private predicate os_path_join(ControlFlowNode fromnode, CallNode tonode) { + exists(FunctionObject path_join | + exists(ModuleObject os | os.getName() = "os" | + os.getAttribute("path").(ModuleObject).getAttribute("join") = path_join + ) | + tonode = path_join.getACall() and tonode.getAnArg() = fromnode + ) +} diff --git a/python/ql/src/semmle/python/security/strings/Common.qll b/python/ql/src/semmle/python/security/strings/Common.qll new file mode 100644 index 000000000000..12b73acad8b8 --- /dev/null +++ b/python/ql/src/semmle/python/security/strings/Common.qll @@ -0,0 +1,16 @@ +import python + + +/* A call that returns a copy (or similar) of the argument */ +predicate copy_call(ControlFlowNode fromnode, CallNode tonode) { + tonode.getFunction().(AttrNode).getObject("copy") = fromnode + or + exists(ModuleObject copy, string name | + name = "copy" or name = "deepcopy" | + copy.getAttribute(name).(FunctionObject).getACall() = tonode and + tonode.getArg(0) = fromnode + ) + or + tonode.getFunction().refersTo(builtin_object("reversed")) and + tonode.getArg(0) = fromnode +} diff --git a/python/ql/src/semmle/python/security/strings/External.qll b/python/ql/src/semmle/python/security/strings/External.qll new file mode 100644 index 000000000000..e1f0406c6913 --- /dev/null +++ b/python/ql/src/semmle/python/security/strings/External.qll @@ -0,0 +1,98 @@ +import python +import Basic +private import Common + +/** An extensible kind of taint representing an externally controlled string. + */ +abstract class ExternalStringKind extends StringKind { + + bindingset[this] + ExternalStringKind() { + this = this + } + + override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { + result = StringKind.super.getTaintForFlowStep(fromnode, tonode) + or + tonode.(SequenceNode).getElement(_) = fromnode and result.(ExternalStringSequenceKind).getItem() = this + or + json_load(fromnode, tonode) and result.(ExternalJsonKind).getValue() = this + or + tonode.(DictNode).getAValue() = fromnode and result.(ExternalStringDictKind).getValue() = this + } + +} + +/** A kind of "taint", representing a sequence, with a "taint" member */ +class ExternalStringSequenceKind extends SequenceKind { + + ExternalStringSequenceKind() { + this.getItem() instanceof ExternalStringKind + } + +} + +/** An hierachical dictionary or list where the entire structure is externally controlled + * This is typically a parsed JSON object. + */ +class ExternalJsonKind extends TaintKind { + + ExternalJsonKind() { + this = "json[" + any(ExternalStringKind key) + "]" + } + + + /** Gets the taint kind for item in this sequence */ + TaintKind getValue() { + this = "json[" + result + "]" + or + result = this + } + + override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { + this.taints(fromnode) and + json_subscript_taint(tonode, fromnode, this, result) + or + result = this and copy_call(fromnode, tonode) + } + + override TaintKind getTaintOfMethodResult(string name) { + name = "get" and result = this.getValue() + } + +} + +/** A kind of "taint", representing a dictionary mapping str->"taint" */ +class ExternalStringDictKind extends DictKind { + + ExternalStringDictKind() { + this.getValue() instanceof ExternalStringKind + } + +} + +/** A kind of "taint", representing a dictionary mapping strings to sequences of + * tainted strings */ + +class ExternalStringSequenceDictKind extends DictKind { + ExternalStringSequenceDictKind() { + this.getValue() instanceof ExternalStringSequenceKind + } +} + +/* Helper for getTaintForStep() */ +pragma [noinline] +private predicate json_subscript_taint(SubscriptNode sub, ControlFlowNode obj, ExternalJsonKind seq, TaintKind key) { + sub.isLoad() and + sub.getValue() = obj and + key = seq.getValue() +} + + +private predicate json_load(ControlFlowNode fromnode, CallNode tonode) { + exists(FunctionObject json_loads | + any(ModuleObject json | json.getName() = "json").getAttribute("loads") = json_loads and + json_loads.getACall() = tonode and tonode.getArg(0) = fromnode + ) +} + diff --git a/python/ql/src/semmle/python/security/strings/Untrusted.qll b/python/ql/src/semmle/python/security/strings/Untrusted.qll new file mode 100644 index 000000000000..65e624aa1b7f --- /dev/null +++ b/python/ql/src/semmle/python/security/strings/Untrusted.qll @@ -0,0 +1,14 @@ +import python +import External + + +/** A kind of taint representing an externally controlled string. + * This class is a simple sub-class of `ExternalStringKind`. + */ +class UntrustedStringKind extends ExternalStringKind { + + UntrustedStringKind() { + this = "externally controlled string" + } + +} diff --git a/python/ql/src/semmle/python/strings.qll b/python/ql/src/semmle/python/strings.qll new file mode 100644 index 000000000000..b35f831c2487 --- /dev/null +++ b/python/ql/src/semmle/python/strings.qll @@ -0,0 +1,59 @@ +import python + +predicate format_string(StrConst e) { + exists(BinaryExpr b | b.getOp() instanceof Mod and b.getLeft() = e) +} + +predicate mapping_format(StrConst e) { + conversion_specifier(e, _).regexpMatch("%\\([A-Z_a-z0-9]+\\).*") +} + +/* +MAPPING_KEY = "(\\([^)]+\\))?" +CONVERSION_FLAGS = "[#0\\- +]?" +MINIMUM_FIELD_WIDTH = "(\\*|[0-9]*)" +PRECISION = "(\\.(\\*|[0-9]*))?" +LENGTH_MODIFIER = "[hlL]?" +TYPE = "[bdiouxXeEfFgGcrs%]" +*/ + +private +string conversion_specifier_string(StrConst e, int number, int position) { + exists(string s, string REGEX | s = e.getText() | + REGEX = "%(\\([^)]*\\))?[#0\\- +]*(\\*|[0-9]*)(\\.(\\*|[0-9]*))?(h|H|l|L)?[badiouxXeEfFgGcrs%]" and + result = s.regexpFind(REGEX, number, position)) +} + +private +string conversion_specifier(StrConst e, int number) { + result = conversion_specifier_string(e, number, _) and result != "%%" +} + +int illegal_conversion_specifier(StrConst e) { + format_string(e) and + "%" = e.getText().charAt(result) and + // not the start of a conversion specifier or the second % of a %% + not exists(conversion_specifier_string(e, _, result)) and + not exists(conversion_specifier_string(e, _, result - 1)) +} + +/** Gets the number of format items in a format string */ +int format_items(StrConst e) { + result = count(int i | | conversion_specifier(e, i)) + + // a conversion specifier uses an extra item for each * + count(int i, int j | conversion_specifier(e, i).charAt(j) = "*") +} + +private string str(Expr e) { + result = ((Num)e).getN() + or + result = "'" + ((StrConst)e).getText() + "'" +} + +/** Gets a string representation of an expression more suited for embedding in message strings than .toString() */ +string repr(Expr e) { + if exists(str(e)) then + result = str(e) + else + result = e.toString() +} diff --git a/python/ql/src/semmle/python/templates/PyxlTags.qll b/python/ql/src/semmle/python/templates/PyxlTags.qll new file mode 100644 index 000000000000..3ab3f8980bcc --- /dev/null +++ b/python/ql/src/semmle/python/templates/PyxlTags.qll @@ -0,0 +1,107 @@ + + +import python + +/** A Tag in Pyxl (which gets converted to a call in Python). + * + */ +class PyxlTag extends Call { + + PyxlTag() { + pyxl_tag(this, _) + } + + string getPyxlTagName() { + pyxl_tag(this, result) + } + + /** Gets the pyxl or Python node that is enclosed by this one in the pyxl source */ + Expr getEnclosedNode() { + none() + } + + /** Gets the Python code (if any) that is contained in this pyxl node */ + Expr getEnclosedPythonCode() { + result = this.getEnclosedNode() and not result instanceof PyxlTag + or + result = ((PyxlTag)this.getEnclosedNode()).getEnclosedPythonCode() + } + +} + +private predicate pyxl_tag(Call c, string name) { + exists(Attribute tag, Name html | + tag = c.getFunc() and + html = tag.getObject() and + name = tag.getName() and + html.getId() = "html" + ) +} + +class PyxlHtmlTag extends PyxlTag { + + PyxlHtmlTag() { + this.getPyxlTagName().prefix(2) = "x_" + } + + string getTagName() { + result = this.getPyxlTagName().suffix(2) + } + + /** Html tags get transformed into a call. This node is the callee function and the enclosed node is an argument. */ + override Expr getEnclosedNode() { + exists(Call c | + c.getFunc() = this and + result = c.getAnArg() + ) + } + +} + +class PyxlIfTag extends PyxlTag { + + PyxlIfTag() { + this.getPyxlTagName() = "_push_condition" + } + + override Expr getEnclosedNode() { + result = this.getAnArg() + } +} + +class PyxlEndIfTag extends PyxlTag { + + PyxlEndIfTag() { + this.getPyxlTagName() = "_leave_if" + } + + override Expr getEnclosedNode() { + result = this.getAnArg() + } + +} + +class PyxlRawHtml extends PyxlTag{ + + PyxlRawHtml() { + this.getPyxlTagName() = "rawhtml" + } + + /** The text for this raw html, if it is simple text. */ + string getText() { + exists(Unicode text | + text = this.getValue() and + result = text.getS() + ) + } + + Expr getValue() { + result = this.getArg(0) + } + + override Expr getEnclosedNode() { + result = this.getAnArg() + } + +} + diff --git a/python/ql/src/semmle/python/templates/Templates.qll b/python/ql/src/semmle/python/templates/Templates.qll new file mode 100644 index 000000000000..2aec12119a28 --- /dev/null +++ b/python/ql/src/semmle/python/templates/Templates.qll @@ -0,0 +1,24 @@ +import python + + +abstract class Template extends Module { + +} + +class SpitfireTemplate extends Template { + + SpitfireTemplate() { + this.getKind() = "Spitfire template" + } + +} + +class PyxlModule extends Template { + + PyxlModule() { + exists(Comment c | c.getLocation().getFile() = this.getFile() | + c.getText().regexpMatch("# *coding.*pyxl.*") + ) + } + +} diff --git a/python/ql/src/semmle/python/types/ClassObject.qll b/python/ql/src/semmle/python/types/ClassObject.qll new file mode 100644 index 000000000000..2aa5e9f4a920 --- /dev/null +++ b/python/ql/src/semmle/python/types/ClassObject.qll @@ -0,0 +1,612 @@ +import python +private import semmle.python.pointsto.PointsTo +private import semmle.python.pointsto.Base +private import semmle.python.pointsto.MRO as MRO + +predicate is_c_metaclass(Object o) { + py_special_objects(o, "type") + or + exists(Object sup | py_cmembers_versioned(o, ".super.", sup, major_version().toString()) and is_c_metaclass(sup)) +} + + +library class ObjectOrCfg extends @py_object { + + string toString() { + /* Not to be displayed */ + none() + } + + ControlFlowNode getOrigin() { + result = this + } + +} + +/** A class whose instances represents Python classes. + * Instances of this class represent either builtin classes + * such as `list` or `str`, or program-defined Python classes + * present in the source code. + * + * Generally there is a one-to-one mapping between classes in + * the Python program and instances of this class in the database. + * However, that is not always the case. For example, dynamically + * generated classes may share a single QL class instance. + * Also the existence of a class definition in the source code + * does not guarantee that such a class will ever exist in the + * running program. + */ +class ClassObject extends Object { + + ClassObject() { + this.getOrigin() instanceof ClassExpr or + py_cobjecttypes(_, this) or + exists(Object meta | py_cobjecttypes(this, meta) and is_c_metaclass(meta)) or + py_special_objects(this, "_semmle_unknown_type") + } + + private predicate isStr() { + py_special_objects(this, "bytes") and major_version() = 2 + or + py_special_objects(this, "unicode") and major_version() = 3 + } + + /** Gets the short (unqualified) name of this class */ + string getName() { + this.isStr() and result = "str" + or + not this.isStr() and py_cobjectnames(this, result) and not this = theUnknownType() + or + result = this.getPyClass().getName() + } + + /** Gets the qualified name for this class. + * Should return the same name as the `__qualname__` attribute on classes in Python 3. + */ + string getQualifiedName() { + py_cobjectnames(this, _) and result = this.getName() + or + result = this.getPyClass().getQualifiedName() + } + + /** Gets the nth base class of this class */ + Object getBaseType(int n) { + result = PointsTo::Types::class_base_type(this, n) + } + + /** Gets a base class of this class */ + Object getABaseType() { + result = this.getBaseType(_) + } + + /** Whether this class has a base class */ + predicate hasABase() { + exists(ClassExpr cls | this.getOrigin() = cls | exists(cls.getABase())) + or + /* The extractor uses the special name ".super." to indicate the super class of a builtin class */ + py_cmembers_versioned(this, ".super.", _, major_version().toString()) + } + + /** Gets a super class of this class (includes transitive super classes) */ + ClassObject getASuperType() { + result = PointsTo::Types::get_a_super_type(this) + } + + /** Gets a super class of this class (includes transitive super classes) or this class */ + ClassObject getAnImproperSuperType() { + result = PointsTo::Types::get_an_improper_super_type(this) + } + + /** Whether this class is a new style class. + A new style class is one that implicitly or explicitly inherits from `object`. */ + predicate isNewStyle() { + PointsTo::Types::is_new_style(this) + } + + /** Whether this class is a legal exception class. + * What constitutes a legal exception class differs between major versions */ + predicate isLegalExceptionType() { + not this.isNewStyle() or + this.getAnImproperSuperType() = theBaseExceptionType() + or + major_version() = 2 and this = theTupleType() + } + + /** Gets the scope associated with this class, if it is not a builtin class */ + Class getPyClass() { + result.getClassObject() = this + } + + /** Returns an attribute declared on this class (not on a super-class) */ + Object declaredAttribute(string name) { + PointsTo::Types::class_declared_attribute(this, name, result, _, _) + } + + /** Returns an attribute declared on this class (not on a super-class) */ + predicate declaresAttribute(string name) { + class_declares_attribute(this, name) + } + + /** Returns an attribute as it would be when looked up at runtime on this class. + Will include attributes of super-classes */ + Object lookupAttribute(string name) { + result = this.getMro().lookup(name) + } + + MRO::ClassList getMro() { + PointsTo::Types::is_new_style_bool(this) = true and + result = MRO::new_style_mro(this) + or + result = MRO::old_style_mro(this) + } + + /** Looks up an attribute by searching this class' MRO starting at `start` */ + Object lookupMro(ClassObject start, string name) { + result = this.getMro().startingAt(start).lookup(name) + } + + /** Whether the named attribute refers to the object and origin */ + predicate attributeRefersTo(string name, Object obj, ControlFlowNode origin) { + PointsTo::Types::class_attribute_lookup(this, name, obj, _, origin) + } + + /** Whether the named attribute refers to the object, class and origin */ + predicate attributeRefersTo(string name, Object obj, ClassObject cls, ControlFlowNode origin) { + not obj = unknownValue() and + PointsTo::Types::class_attribute_lookup(this, name, obj, cls, origin) + } + + /** Whether this class has a attribute named `name`, either declared or inherited.*/ + predicate hasAttribute(string name) { + PointsTo::Types::class_has_attribute(this, name) + } + + /** Whether it is impossible to know all the attributes of this class. Usually because it is + impossible to calculate the full class hierarchy or because some attribute is too dynamic. */ + predicate unknowableAttributes() { + /* True for a class with undeterminable superclasses, unanalysable metaclasses, or other confusions */ + this.failedInference() + or + this.getMetaClass().failedInference() + or + exists(Object base | + base = this.getABaseType() | + base.(ClassObject).unknowableAttributes() + or + not base instanceof ClassObject + ) + } + + /** Gets the metaclass for this class */ + ClassObject getMetaClass() { + result = PointsTo::Types::class_get_meta_class(this) + and + not this.failedInference() + } + + /* Whether this class is abstract. */ + predicate isAbstract() { + this.getMetaClass() = theAbcMetaClassObject() + or + exists(FunctionObject f, Raise r, Name ex | + f = this.lookupAttribute(_) and + r.getScope() = f.getFunction() | + (r.getException() = ex or r.getException().(Call).getFunc() = ex) and + (ex.getId() = "NotImplementedError" or ex.getId() = "NotImplemented") + ) + } + + ControlFlowNode declaredMetaClass() { + result = this.getPyClass().getMetaClass().getAFlowNode() + } + + /** Has type inference failed to compute the full class hierarchy for this class for the reason given. */ + predicate failedInference(string reason) { + PointsTo::Types::failed_inference(this, reason) + } + + /** Has type inference failed to compute the full class hierarchy for this class */ + predicate failedInference() { + this.failedInference(_) + } + + /** Gets an object which is the sole instance of this class, if this class is probably a singleton. + * Note the 'probable' in the name; there is no guarantee that this class is in fact a singleton. + * It is guaranteed that getProbableSingletonInstance() returns at most one Object for each ClassObject. */ + Object getProbableSingletonInstance() { + exists(ControlFlowNode use, Expr origin | + use.refersTo(result, this, origin.getAFlowNode()) + | + this.hasStaticallyUniqueInstance() + and + /* Ensure that original expression will be executed only one. */ + origin.getScope() instanceof ImportTimeScope and + not exists(Expr outer | outer.getASubExpression+() = origin) + ) + or + this = theNoneType() and result = theNoneObject() + } + + /** This class is only instantiated at one place in the code */ + private predicate hasStaticallyUniqueInstance() { + strictcount(Object instances | PointsTo::points_to(_, _, instances, this, _)) = 1 + } + + ImportTimeScope getImportTimeScope() { + result = this.getPyClass() + } + + override string toString() { + this.isC() and result = "builtin-class " + this.getName() and not this = theUnknownType() + or + not this.isC() and result = "class " + this.getName() + } + + /* Method Resolution Order */ + + /** Returns the next class in the MRO of 'this' after 'sup' */ + ClassObject nextInMro(ClassObject sup) { + exists(MRO::ClassList mro, int i | + mro = this.getMro() and + sup = mro.getItem(i) and + result = mro.getItem(i+1) + ) and + not this.failedInference() + } + + /** Gets the MRO for this class. ClassObject `sup` occurs at `index` in the list of classes. + * `this` has an index of `1`, the next class in the MRO has an index of `2`, and so on. + */ + ClassObject getMroItem(int index) { + result = this.getMro().getItem(index) + } + + /** Holds if this class has duplicate base classes */ + predicate hasDuplicateBases() { + exists(ClassObject base, int i, int j | i != j and base = this.getBaseType(i) and base = this.getBaseType(j)) + } + + /** Holds if this class is an iterable. */ + predicate isIterable() { + this.hasAttribute("__iter__") or this.hasAttribute("__getitem__") + } + + /** Holds if this class is an iterator. */ + predicate isIterator() { + this.hasAttribute("__iter__") and + (major_version() = 3 and this.hasAttribute("__next__") + or + /* Because 'next' is a common method name we need to check that an __iter__ + * method actually returns this class. This is not needed for Py3 as the + * '__next__' method exists to define a class as an iterator. + */ + major_version() = 2 and this.hasAttribute("next") and + exists(ClassObject other, FunctionObject iter | + other.declaredAttribute("__iter__") = iter | + iter.getAnInferredReturnType() = this + ) + ) + or + /* This will be redundant when we have C class information */ + this = theGeneratorType() + } + + /** Holds if this class is an improper subclass of the other class. + * True if this is a sub-class of other or this is the same class as other. + * + * Equivalent to the Python builtin function issubclass(). + */ + predicate isSubclassOf(ClassObject other) { + this = other or this.getASuperType() = other + } + + /** Synonymous with isContainer(), retained for backwards compatibility. */ + predicate isCollection() { + this.isContainer() + } + + /** Holds if this class is a container(). That is, does it have a __getitem__ method.*/ + predicate isContainer() { + exists(this.lookupAttribute("__getitem__")) + } + + /** Holds if this class is a mapping. */ + predicate isMapping() { + exists(this.lookupAttribute("__getitem__")) + and + not this.isSequence() + } + + /** Holds if this class is probably a sequence. */ + predicate isSequence() { + /* To determine whether something is a sequence or a mapping is not entirely clear, + * so we need to guess a bit. + */ + this.getAnImproperSuperType() = theTupleType() + or + this.getAnImproperSuperType() = theListType() + or + this.getAnImproperSuperType() = theRangeType() + or + this.getAnImproperSuperType() = theBytesType() + or + this.getAnImproperSuperType() = theUnicodeType() + or + /* Does this inherit from abc.Sequence? */ + this.getASuperType().getName() = "Sequence" + or + /* Does it have an index or __reversed__ method? */ + this.isContainer() and + ( + this.hasAttribute("index") or + this.hasAttribute("__reversed__") + ) + } + + predicate isCallable() { + this.hasAttribute("__call__") + } + + predicate isContextManager() { + this.hasAttribute("__enter__") and this.hasAttribute("__exit__") + } + + predicate assignedInInit(string name) { + exists(FunctionObject init | init = this.lookupAttribute("__init__") | + attribute_assigned_in_method(init, name) + ) + } + + /** Holds if this class is unhashable */ + predicate unhashable() { + this.lookupAttribute("__hash__") = theNoneObject() + or + ((FunctionObject)this.lookupAttribute("__hash__")).neverReturns() + } + + /** Holds if this class is a descriptor */ + predicate isDescriptorType() { + this.hasAttribute("__get__") + } + + /** Holds if this class is an overriding descriptor */ + predicate isOverridingDescriptorType() { + this.hasAttribute("__get__") and this.hasAttribute("__set__") + } + + FunctionObject getAMethodCalledFromInit() { + exists(FunctionObject init | + init = this.lookupAttribute("__init__") and + init.getACallee*() = result + ) + } + + override boolean booleanValue() { + result = true + } + + /** Gets a call to this class. Note that the call may not create a new instance of + * this class, as that depends on the `__new__` method of this class. + */ + CallNode getACall() { + result.getFunction().refersTo(this) + } + +} + +/** The 'str' class. This is the same as the 'bytes' class for + * Python 2 and the 'unicode' class for Python 3 */ +ClassObject theStrType() { + if major_version() = 2 then + result = theBytesType() + else + result = theUnicodeType() +} + +private +Module theAbcModule() { + result.getName() = "abc" +} + +ClassObject theAbcMetaClassObject() { + /* Avoid using points-to and thus negative recursion */ + exists(Class abcmeta | + result.getPyClass() = abcmeta | + abcmeta.getName() = "ABCMeta" and + abcmeta.getScope() = theAbcModule() + ) +} + +/* Common builtin classes */ + +/** The built-in class NoneType*/ +ClassObject theNoneType() { + py_special_objects(result, "NoneType") +} + +/** The built-in class 'bool' */ +ClassObject theBoolType() { + py_special_objects(result, "bool") +} + +/** The builtin class 'type' */ +ClassObject theTypeType() { + py_special_objects(result, "type") +} + +/** The builtin object ClassType (for old-style classes) */ +ClassObject theClassType() { + py_special_objects(result, "ClassType") +} + +/** The builtin object InstanceType (for old-style classes) */ +ClassObject theInstanceType() { + py_special_objects(result, "InstanceType") +} + +/** The builtin class 'tuple' */ +ClassObject theTupleType() { + py_special_objects(result, "tuple") +} + +/** The builtin class 'int' */ +ClassObject theIntType() { + py_special_objects(result, "int") +} + +/** The builtin class 'long' (Python 2 only) */ +ClassObject theLongType() { + py_special_objects(result, "long") +} + +/** The builtin class 'float' */ +ClassObject theFloatType() { + py_special_objects(result, "float") +} + +/** The builtin class 'complex' */ +ClassObject theComplexType() { + py_special_objects(result, "complex") +} + +/** The builtin class 'object' */ +ClassObject theObjectType() { + py_special_objects(result, "object") +} + +/** The builtin class 'list' */ +ClassObject theListType() { + py_special_objects(result, "list") +} + +/** The builtin class 'dict' */ + +ClassObject theDictType() { + py_special_objects(result, "dict") +} + +/** The builtin class 'Exception' */ + +ClassObject theExceptionType() { + py_special_objects(result, "Exception") +} + +/** The builtin class for unicode. unicode in Python2, str in Python3 */ +ClassObject theUnicodeType() { + py_special_objects(result, "unicode") +} + +/** The builtin class '(x)range' */ +ClassObject theRangeType() { + result = builtin_object("xrange") + or + major_version() = 3 and result = builtin_object("range") +} + +/** The builtin class for bytes. str in Python2, bytes in Python3 */ +ClassObject theBytesType() { + py_special_objects(result, "bytes") +} + +/** The builtin class 'set' */ +ClassObject theSetType() { + py_special_objects(result, "set") +} + +/** The builtin class 'property' */ +ClassObject thePropertyType() { + py_special_objects(result, "property") +} + +/** The builtin class 'BaseException' */ +ClassObject theBaseExceptionType() { + py_special_objects(result, "BaseException") +} + +/** The class of builtin-functions */ +ClassObject theBuiltinFunctionType() { + py_special_objects(result, "BuiltinFunctionType") +} + +/** The class of Python functions */ +ClassObject thePyFunctionType() { + py_special_objects(result, "FunctionType") +} + +/** The builtin class 'classmethod' */ +ClassObject theClassMethodType() { + py_special_objects(result, "ClassMethod") +} + +/** The builtin class 'staticmethod' */ +ClassObject theStaticMethodType() { + py_special_objects(result, "StaticMethod") +} + +/** The class of modules */ +ClassObject theModuleType() { + py_special_objects(result, "ModuleType") +} + +/** The class of generators */ +ClassObject theGeneratorType() { + py_special_objects(result, "generator") +} + +/** The builtin class 'TypeError' */ +ClassObject theTypeErrorType() { + py_special_objects(result, "TypeError") +} + +/** The builtin class 'AttributeError' */ +ClassObject theAttributeErrorType() { + py_special_objects(result, "AttributeError") +} + +/** The builtin class 'KeyError' */ +ClassObject theKeyErrorType() { + py_special_objects(result, "KeyError") +} + +/** The builtin class of bound methods */ +pragma [noinline] +ClassObject theBoundMethodType() { + py_special_objects(result, "MethodType") +} + +/** The builtin class of builtin properties */ +ClassObject theGetSetDescriptorType() { + py_special_objects(result, "GetSetDescriptorType") +} + +/** The method descriptor class */ +ClassObject theMethodDescriptorType() { + py_special_objects(result, "MethodDescriptorType") +} + +/** The class of builtin properties */ +ClassObject theBuiltinPropertyType() { + /* This is CPython specific */ + result.isC() and + result.getName() = "getset_descriptor" +} + +/** The builtin class 'IOError' */ +ClassObject theIOErrorType() { + result = builtin_object("IOError") +} + +/** The builtin class 'super' */ +ClassObject theSuperType() { + result = builtin_object("super") +} + +/** The builtin class 'StopIteration' */ +ClassObject theStopIterationType() { + result = builtin_object("StopIteration") +} + +/** The builtin class 'NotImplementedError' */ +ClassObject theNotImplementedErrorType() { + result = builtin_object("NotImplementedError") +} diff --git a/python/ql/src/semmle/python/types/Descriptors.qll b/python/ql/src/semmle/python/types/Descriptors.qll new file mode 100644 index 000000000000..168597d0eed9 --- /dev/null +++ b/python/ql/src/semmle/python/types/Descriptors.qll @@ -0,0 +1,59 @@ +import python +private import semmle.python.pointsto.PointsTo + +/** A bound method object, x.f where type(x) has a method f */ +class BoundMethod extends Object { + + BoundMethod() { + bound_method(this, _) + } + + /* Gets the method 'f' in 'x.f' */ + FunctionObject getMethod() { + bound_method(this, result) + } + +} + +private predicate bound_method(AttrNode binding, FunctionObject method) { + binding = method.getAMethodCall().getFunction() +} + +private predicate decorator_call(Object method, ClassObject decorator, FunctionObject func) { + exists(CallNode f | + method = f and + f.getFunction().refersTo(decorator) and + PointsTo::points_to(f.getArg(0), _, func, _, _) + ) +} + +/** A class method object. Either a decorated function or an explicit call to classmethod(f) */ +class ClassMethodObject extends Object { + + ClassMethodObject() { + PointsTo::class_method(this, _) + } + + FunctionObject getFunction() { + PointsTo::class_method(this, result) + } + + CallNode getACall() { + PointsTo::class_method_call(this, _, _, _, result) + } + +} + +/** A static method object. Either a decorated function or an explicit call to staticmethod(f) */ +class StaticMethodObject extends Object { + + StaticMethodObject() { + decorator_call(this, theStaticMethodType(), _) + } + + FunctionObject getFunction() { + decorator_call(this, theStaticMethodType(), result) + } + +} + diff --git a/python/ql/src/semmle/python/types/Exceptions.qll b/python/ql/src/semmle/python/types/Exceptions.qll new file mode 100644 index 000000000000..485c0112116e --- /dev/null +++ b/python/ql/src/semmle/python/types/Exceptions.qll @@ -0,0 +1,331 @@ +/** + * Analysis of exception raising and handling. + * + * In order to make this useful we make a number of assumptions. These are: + * 1. Typing errors (TypeError, NameError, AttributeError) are assumed to occur only if: + * a) Explicitly raised, e.g. raise TypeError() + * or + * b) Explicitly caught, e.g. except TypeError: + * 2. Asynchronous exceptions, MemoryError, KeyboardInterrupt are ignored. + * 3. Calls to unidentified objects can raise anything, unless it is an + * attribute named 'read' or 'write' in which case it can raise IOError. + */ + +import python + +/** Subset of ControlFlowNodes which might raise an exception */ +class RaisingNode extends ControlFlowNode { + + RaisingNode() { + exists(this.getAnExceptionalSuccessor()) + or + this.isExceptionalExit(_) + } + + /** Gets the CFG node for the exception, if and only if this RaisingNode is an explicit raise */ + ControlFlowNode getExceptionNode() { + exists(Raise r | + r = this.getNode() and result.getNode() = r.getRaised() and + result.getBasicBlock().dominates(this.getBasicBlock()) + ) + } + + private predicate quits() { + this.(CallNode).getFunction().refersTo(quitterObject(_)) + } + + /** Gets the type of an exception that may be raised + at this control flow node */ + ClassObject getARaisedType() { + result = this.localRaisedType() + or + exists(FunctionObject func | + this = func.getACall() | + result = func.getARaisedType() + ) + or + result = systemExitRaise() + } + + pragma[noinline] + private ClassObject systemExitRaise() { + this.quits() and result = builtin_object("SystemExit") + } + + pragma [noinline, nomagic] + private ClassObject localRaisedType() { + result.isSubclassOf(theBaseExceptionType()) + and + ( + exists(ControlFlowNode ex | + ex = this.getExceptionNode() and + (ex.refersTo(result) or ex.refersTo(_, result, _)) + ) + or + this.getNode() instanceof ImportExpr and result = builtin_object("ImportError") + or + this.getNode() instanceof Print and result = theIOErrorType() + or + exists(ExceptFlowNode except | + except = this.getAnExceptionalSuccessor() and + except.handles(result) and + result = this.innateException() + ) + or + not exists(ExceptFlowNode except | except = this.getAnExceptionalSuccessor()) + and + sequence_or_mapping(this) and result = theLookupErrorType() + or + this.read_write_call() and result = theIOErrorType() + ) + } + + pragma [noinline] + ClassObject innateException() { + this.getNode() instanceof Attribute and result = theAttributeErrorType() + or + this.getNode() instanceof Name and result = theNameErrorType() + or + this.getNode() instanceof Subscript and result = theIndexErrorType() + or + this.getNode() instanceof Subscript and result = theKeyErrorType() + } + + /** Whether this control flow node raises an exception, + * but the type of the exception it raises cannot be inferred. */ + predicate raisesUnknownType() { + /* read/write calls are assumed to raise IOError (OSError for Py3) */ + not this.read_write_call() + and + ( + /* Call to an unknown object */ + this.getNode() instanceof Call and not exists(FunctionObject func | this = func.getACall()) + and not exists(ClassObject known | this.(CallNode).getFunction().refersTo(known)) + or + this.getNode() instanceof Exec + or + /* Call to a function raising an unknown type */ + exists(FunctionObject func | + this = func.getACall() | + func.raisesUnknownType() + ) + ) + } + + private predicate read_write_call() { + exists(string mname | mname = this.(CallNode).getFunction().(AttrNode).getName() | + mname = "read" or mname = "write" + ) + } + + /** Whether (as inferred by type inference) it is highly unlikely (or impossible) for control to flow from this to succ. + */ + predicate unlikelySuccessor(ControlFlowNode succ) { + succ = this.getAnExceptionalSuccessor() and + not this.viableExceptionEdge(succ, _) and + not this.raisesUnknownType() + or + exists(FunctionObject func | + func.getACall() = this and + func.neverReturns() and + succ = this.getASuccessor() and + not succ = this.getAnExceptionalSuccessor() and + // If result is yielded then func is likely to be some form of coroutine. + not succ.getNode() instanceof Yield + ) + or + this.quits() and + succ = this.getASuccessor() and + not succ = this.getAnExceptionalSuccessor() + } + + /** Whether it is considered plausible that 'raised' can be raised across the edge this-succ */ + predicate viableExceptionEdge(ControlFlowNode succ, ClassObject raised) { + raised.isLegalExceptionType() and + raised = this.getARaisedType() and + succ = this.getAnExceptionalSuccessor() and + ( + /* An 'except' that handles raised and there is no more previous handler */ + ((ExceptFlowNode)succ).handles(raised) and + not exists(ExceptFlowNode other, StmtList s, int i, int j | + not other = succ and other.handles(raised) and + s.getItem(i) = succ.getNode() and s.getItem(j) = other.getNode() + | + j < i + ) + or + /* Any successor that is not an 'except', provided that 'raised' is not handled by a different successor. */ + (not ((ExceptFlowNode)this.getAnExceptionalSuccessor()).handles(raised) and + not succ instanceof ExceptFlowNode) + ) + } + + /** Whether this exceptional exit is viable. That is, is it + * plausible that the scope `s` can be exited with exception `raised` + * at this point. + */ + predicate viableExceptionalExit(Scope s, ClassObject raised) { + raised.isLegalExceptionType() and + raised = this.getARaisedType() and + this.isExceptionalExit(s) and + not ((ExceptFlowNode)this.getAnExceptionalSuccessor()).handles(raised) + } + +} + +/** Is this a sequence or mapping subscript x[i]? */ +private predicate sequence_or_mapping(RaisingNode r) { + r.getNode() instanceof Subscript +} + +private predicate current_exception(ClassObject ex, BasicBlock b) { + exists(RaisingNode r | + r.viableExceptionEdge(b.getNode(0), ex) and not b.getNode(0) instanceof ExceptFlowNode + ) + or + exists(BasicBlock prev | + current_exception(ex, prev) and + exists(ControlFlowNode pred, ControlFlowNode succ | + pred = prev.getLastNode() and succ = b.getNode(0) | + pred.getASuccessor() = succ and + (/* Normal control flow */ + not pred.getAnExceptionalSuccessor() = succ or + /* Re-raise the current exception, propagating to the successor */ + pred instanceof ReraisingNode) + ) + ) +} + +private predicate unknown_current_exception(BasicBlock b) { + exists(RaisingNode r | + r.raisesUnknownType() and + r.getAnExceptionalSuccessor() = b.getNode(0) and + not b.getNode(0) instanceof ExceptFlowNode + ) + or + exists(BasicBlock prev | + unknown_current_exception(prev) and + exists(ControlFlowNode pred, ControlFlowNode succ | + pred = prev.getLastNode() and succ = b.getNode(0) | + pred.getASuccessor() = succ and + (not pred.getAnExceptionalSuccessor() = succ or pred instanceof ReraisingNode) + ) + ) +} + +/** INTERNAL -- Use FunctionObject.getARaisedType() instead */ +predicate scope_raises(ClassObject ex, Scope s) { + exists(BasicBlock b | + current_exception(ex, b) and + b.getLastNode().isExceptionalExit(s) | + b.getLastNode() instanceof ReraisingNode + ) + or + exists(RaisingNode r | r.viableExceptionalExit(s, ex)) +} + +/** INTERNAL -- Use FunctionObject.raisesUnknownType() instead */ +predicate scope_raises_unknown(Scope s) { + exists(BasicBlock b | + b.getLastNode() instanceof ReraisingNode + and b.getLastNode().isExceptionalExit(s) | + unknown_current_exception(b) + ) + or + exists(RaisingNode r | + r.raisesUnknownType() and + r.isExceptionalExit(s) + ) +} + + +/** ControlFlowNode for an 'except' statement. */ +class ExceptFlowNode extends ControlFlowNode { + + ExceptFlowNode() { + this.getNode() instanceof ExceptStmt + } + + ControlFlowNode getType() { + exists(ExceptStmt ex | + this.getBasicBlock().dominates(result.getBasicBlock()) and + ex = this.getNode() and result = ex.getType().getAFlowNode() + ) + } + + ControlFlowNode getName() { + exists(ExceptStmt ex | + this.getBasicBlock().dominates(result.getBasicBlock()) and + ex = this.getNode() and result = ex.getName().getAFlowNode() + ) + } + + private predicate handledObject(Object obj, ClassObject cls, ControlFlowNode origin) { + this.getType().refersTo(obj, cls, origin) + or + exists(Object tup | + this.handledObject(tup, theTupleType(), _) | + element_from_tuple(tup).refersTo(obj, cls, origin) + ) + } + + /** Gets the inferred type(s) that are handled by this node, splitting tuples if possible. */ + pragma [noinline] + predicate handledException(Object obj, ClassObject cls, ControlFlowNode origin) { + this.handledObject(obj, cls, origin) and not cls = theTupleType() + or + not exists(this.getNode().(ExceptStmt).getType()) and obj = theBaseExceptionType() and cls = theTypeType() and + origin = this + } + + /** Whether this `except` handles `cls` */ + predicate handles(ClassObject cls) { + exists(ClassObject handled | + this.handledException(handled, _, _) | + cls.getAnImproperSuperType() = handled + ) + } + +} + +private ControlFlowNode element_from_tuple(Object tuple) { + exists(Tuple t | + t = tuple.getOrigin() and result = t.getAnElt().getAFlowNode() + ) +} + +/** A Reraising node is the node at the end of a finally block (on the exceptional branch) + * that reraises the current exception. + */ +class ReraisingNode extends RaisingNode { + + ReraisingNode() { + not this.getNode() instanceof Raise and + in_finally(this) and + forall(ControlFlowNode succ | + succ = this.getASuccessor() | + succ = this.getAnExceptionalSuccessor() + ) + } + + /** Gets a class that may be raised by this node */ + override ClassObject getARaisedType() { + exists(BasicBlock b | + current_exception(result, b) and + b.getNode(_) = this + ) + } + +} + +private predicate in_finally(ControlFlowNode n) { + exists(Stmt f | + exists(Try t | f = t.getAFinalstmt()) | + f = n.getNode() + or + f.containsInScope(n.getNode()) + ) +} + + + diff --git a/python/ql/src/semmle/python/types/Extensions.qll b/python/ql/src/semmle/python/types/Extensions.qll new file mode 100644 index 000000000000..619978ff3255 --- /dev/null +++ b/python/ql/src/semmle/python/types/Extensions.qll @@ -0,0 +1,59 @@ +/** This library allows custom extensions to the points-to analysis to incorporate + * custom domain knowledge into the points-to analysis. + * + * This should be considered an advance feature. Modifying the points-to analysis + * can cause queries to give strange and misleading results, if not done with care. + */ + +import python +private import semmle.python.pointsto.PointsTo +private import semmle.python.pointsto.PointsToContext + +/* Custom Facts. This extension mechanism allows you to add custom + * sources of data to the points-to analysis. + */ + +abstract class CustomPointsToFact extends @py_flow_node { + + string toString() { none() } + + abstract predicate pointsTo(Context context, Object value, ClassObject cls, ControlFlowNode origin); + +} + +/* For backwards compatibility */ +class FinalCustomPointsToFact = CustomPointsToFact; + +abstract class CustomPointsToOriginFact extends CustomPointsToFact { + + abstract predicate pointsTo(Object value, ClassObject cls); + + override predicate pointsTo(Context context, Object value, ClassObject cls, ControlFlowNode origin) { + this.pointsTo(value, cls) and origin = this and context.appliesTo(this) + } + +} + +/* An example */ + +/** Any variable iterating over range or xrange must be an integer */ +class RangeIterationVariableFact extends CustomPointsToFact { + + RangeIterationVariableFact() { + exists(For f, ControlFlowNode iterable | + iterable.getBasicBlock().dominates(this.(ControlFlowNode).getBasicBlock()) and + f.getIter().getAFlowNode() = iterable and + f.getTarget().getAFlowNode() = this and + PointsTo::points_to(iterable, _, theRangeType(), _, _) + ) + } + + override predicate pointsTo(Context context, Object value, ClassObject cls, ControlFlowNode origin) { + value = this and + origin = this and + cls = theIntType() and + context.appliesTo(this) + } +} + + diff --git a/python/ql/src/semmle/python/types/FunctionObject.qll b/python/ql/src/semmle/python/types/FunctionObject.qll new file mode 100644 index 000000000000..dffc14a0e274 --- /dev/null +++ b/python/ql/src/semmle/python/types/FunctionObject.qll @@ -0,0 +1,379 @@ +import python +import semmle.python.types.Exceptions +private import semmle.python.pointsto.PointsTo +private import semmle.python.libraries.Zope +private import semmle.python.pointsto.Base + +/** A function object, whether written in Python or builtin */ +abstract class FunctionObject extends Object { + + predicate isOverridingMethod() { + exists(Object f | this.overrides(f)) + } + + predicate isOverriddenMethod() { + exists(Object f | f.overrides(this)) + } + + Function getFunction() { + result = ((CallableExpr)this.getOrigin()).getInnerScope() + } + + /** This function always returns None, meaning that its return value should be disregarded */ + abstract predicate isProcedure(); + + /** Gets the name of this function */ + abstract string getName(); + + /** Gets a class that may be raised by this function */ + abstract ClassObject getARaisedType(); + + /** Whether this function raises an exception, the class of which cannot be inferred */ + abstract predicate raisesUnknownType(); + + /** Use descriptiveString() instead. */ + deprecated string prettyString() { + result = this.descriptiveString() + } + + /** Gets a longer, more descriptive version of toString() */ + abstract string descriptiveString(); + + /** Gets a call-site from where this function is called as a function */ + CallNode getAFunctionCall() { + PointsTo::function_call(this, _, result) + } + + /** Gets a call-site from where this function is called as a method */ + CallNode getAMethodCall() { + PointsTo::method_call(this, _, result) + } + + /** Gets a call-site from where this function is called */ + ControlFlowNode getACall() { + result = PointsTo::get_a_call(this, _) + } + + /** Gets a call-site from where this function is called, given the `context` */ + ControlFlowNode getACall(Context caller_context) { + result = PointsTo::get_a_call(this, caller_context) + } + + /** Gets the `ControlFlowNode` that will be passed as the nth argument to `this` when called at `call`. + This predicate will correctly handle `x.y()`, treating `x` as the zeroth argument. + */ + ControlFlowNode getArgumentForCall(CallNode call, int n) { + result = PointsTo::get_positional_argument_for_call(this, _, call, n) + } + + /** Gets the `ControlFlowNode` that will be passed as the named argument to `this` when called at `call`. + This predicate will correctly handle `x.y()`, treating `x` as the self argument. + */ + ControlFlowNode getNamedArgumentForCall(CallNode call, string name) { + result = PointsTo::get_named_argument_for_call(this, _, call, name) + } + + /** Whether this function never returns. This is an approximation. + */ + predicate neverReturns() { + PointsTo::function_never_returns(this) + } + + /** Whether this is a "normal" method, that is, it is exists as a class attribute + * which is not wrapped and not the __new__ method. */ + predicate isNormalMethod() { + exists(ClassObject cls, string name | + cls.declaredAttribute(name) = this and + name != "__new__" and + not this.getOrigin() instanceof Lambda + ) + } + + /** Gets the minimum number of parameters that can be correctly passed to this function */ + abstract int minParameters(); + + /** Gets the maximum number of parameters that can be correctly passed to this function */ + abstract int maxParameters(); + + /** Gets a function that this function (directly) calls */ + FunctionObject getACallee() { + exists(ControlFlowNode node | + node.getScope() = this.getFunction() and + result.getACall() = node + ) + } + + /** Gets the qualified name for this function object. + * Should return the same name as the `__qualname__` attribute on functions in Python 3. + */ + abstract string getQualifiedName(); + + /** Whether `name` is a legal argument name for this function */ + bindingset[name] + predicate isLegalArgumentName(string name) { + this.getFunction().getAnArg().asName().getId() = name + or + this.getFunction().getAKeywordOnlyArg().getId() = name + or + this.getFunction().hasKwArg() + } + + /** Gets a class that this function may return */ + ClassObject getAnInferredReturnType() { + result = this.(BuiltinCallable).getAReturnType() + } + + predicate isAbstract() { + this.getARaisedType() = theNotImplementedErrorType() + } + +} + +class PyFunctionObject extends FunctionObject { + + PyFunctionObject() { + this.getOrigin() instanceof CallableExpr + } + + override string toString() { + result = "Function " + this.getName() + } + + override string getName() { + result = ((FunctionExpr)this.getOrigin()).getName() + or + this.getOrigin() instanceof Lambda and result = "lambda" + } + + /** Whether this function is a procedure, that is, it has no explicit return statement and is not a generator function */ + override predicate isProcedure() { + this.getFunction().isProcedure() + } + + override ClassObject getARaisedType() { + scope_raises(result, this.getFunction()) + } + + override predicate raisesUnknownType() { + scope_raises_unknown(this.getFunction()) + } + + /** Gets a control flow node corresponding to the value of a return statement */ + ControlFlowNode getAReturnedNode() { + exists(Return ret | + ret.getScope() = this.getFunction() and + result.getNode() = ret.getValue() + ) + } + + override string descriptiveString() { + if this.getFunction().isMethod() then ( + exists(Class cls | + this.getFunction().getScope() = cls | + result = "method " + this.getQualifiedName() + ) + ) else ( + result = "function " + this.getQualifiedName() + ) + } + + override int minParameters() { + exists(Function f | + f = this.getFunction() and + result = count(f.getAnArg()) - count(f.getDefinition().getArgs().getADefault()) + ) + } + + override int maxParameters() { + exists(Function f | + f = this.getFunction() and + if exists(f.getVararg()) then + result = 2147483647 // INT_MAX + else + result = count(f.getAnArg()) + ) + } + + override string getQualifiedName() { + result = this.getFunction().getQualifiedName() + } + + predicate unconditionallyReturnsParameter(int n) { + exists(SsaVariable pvar | + exists(Parameter p | p = this.getFunction().getArg(n) | + p.asName().getAFlowNode() = pvar.getDefinition() + ) and + exists(NameNode rval | + rval = pvar.getAUse() and + exists(Return r | r.getValue() = rval.getNode()) and + rval.strictlyDominates(rval.getScope().getANormalExit()) + ) + ) + } + + /** Factored out to help join ordering */ + private predicate implicitlyReturns(Object none_, ClassObject noneType) { + noneType = theNoneType() and not this.getFunction().isGenerator() and none_ = theNoneObject() and + ( + not exists(this.getAReturnedNode()) and exists(this.getFunction().getANormalExit()) + or + exists(Return ret | ret.getScope() = this.getFunction() and not exists(ret.getValue())) + ) + } + + /** Gets a class that this function may return */ + override ClassObject getAnInferredReturnType() { + this.getFunction().isGenerator() and result = theGeneratorType() + or + not this.neverReturns() and not this.getFunction().isGenerator() and + ( + this.(PyFunctionObject).getAReturnedNode().refersTo( _, result, _) + or + this.implicitlyReturns(_, result) + ) + } + + ParameterDefinition getParameter(int n) { + result.getDefiningNode().getNode() = this.getFunction().getArg(n) + } + +} + +abstract class BuiltinCallable extends FunctionObject { + + abstract ClassObject getAReturnType(); + + override predicate isProcedure() { + forex(ClassObject rt | + rt = this.getAReturnType() | + rt = theNoneType() + ) + } + + abstract override string getQualifiedName(); + +} + +class BuiltinMethodObject extends BuiltinCallable { + + BuiltinMethodObject() { + py_cobjecttypes(this, theMethodDescriptorType()) + or + py_cobjecttypes(this, theBuiltinFunctionType()) and exists(ClassObject c | py_cmembers_versioned(c, _, this, major_version().toString())) + or + exists(ClassObject wrapper_descriptor | + py_cobjecttypes(this, wrapper_descriptor) and + py_cobjectnames(wrapper_descriptor, "wrapper_descriptor") + ) + } + + override string getQualifiedName() { + exists(ClassObject cls | + py_cmembers_versioned(cls, _, this, major_version().toString()) | + result = cls.getName() + "." + this.getName() + ) + or + not exists(ClassObject cls | py_cmembers_versioned(cls, _, this, major_version().toString())) and + result = this.getName() + } + + override string descriptiveString() { + result = "builtin-method " + this.getQualifiedName() + } + + override string getName() { + py_cobjectnames(this, result) + } + + override string toString() { + result = "Builtin-method " + this.getName() + } + + override ClassObject getARaisedType() { + /* Information is unavailable for C code in general */ + none() + } + + override predicate raisesUnknownType() { + /* Information is unavailable for C code in general */ + any() + } + + override int minParameters() { + none() + } + + override int maxParameters() { + none() + } + + override ClassObject getAReturnType() { + ext_rettype(this, result) + } + +} + +class BuiltinFunctionObject extends BuiltinCallable { + + BuiltinFunctionObject() { + py_cobjecttypes(this, theBuiltinFunctionType()) and exists(ModuleObject m | py_cmembers_versioned(m, _, this, major_version().toString())) + } + + override string getName() { + py_cobjectnames(this, result) + } + + override string getQualifiedName() { + result = this.getName() + } + + override string toString() { + result = "Builtin-function " + this.getName() + } + + override string descriptiveString() { + result = "builtin-function " + this.getName() + } + + override ClassObject getARaisedType() { + /* Information is unavailable for C code in general */ + none() + } + + override predicate raisesUnknownType() { + /* Information is unavailable for C code in general */ + any() + } + + override ClassObject getAReturnType() { + /* Enumerate the types of a few builtin functions, that the CPython analysis misses. + */ + this = builtin_object("hex") and result = theStrType() + or + this = builtin_object("oct") and result = theStrType() + or + this = builtin_object("intern") and result = theStrType() + or + /* Fix a few minor inaccuracies in the CPython analysis */ + ext_rettype(this, result) and not ( + this = builtin_object("__import__") and result = theNoneType() + or + this = builtin_object("compile") and result = theNoneType() + or + this = builtin_object("sum") + or + this = builtin_object("filter") + ) + } + + override int minParameters() { + none() + } + + override int maxParameters() { + none() + } + +} + + diff --git a/python/ql/src/semmle/python/types/IgnoredAndApproximations.txt b/python/ql/src/semmle/python/types/IgnoredAndApproximations.txt new file mode 100644 index 000000000000..2b2540e35ee6 --- /dev/null +++ b/python/ql/src/semmle/python/types/IgnoredAndApproximations.txt @@ -0,0 +1,47 @@ +List of approximations used and semantic details that are ignored +================================================================= + +1. Metaclass __getattribute___ + +Attribute lookup on a class, but not its instances, used the __getattribute__() +method of the metaclass. I doubt anyone every overrides this method except for debugging +or testing frameworks, so we ignore the possibility that for a class C +(type(C)).__getattribute__ != type.__getattribute__ + +2. Class __getattribute__ +Many analyses are context-insensitive. For those analyses any instance of a class that +defines __getattribute__ are treated as having unknowable attributes. + +3. Class and Function descriptors +Class and Function descriptors provide a challenge. +The resulting entity is the result of calling the descriptor with the function as input: +@dec +def f(): pass +is equivalent to f = dec(f) + +and decorators themselves can be the result of calling a higher-order function +and can, also be themselves decorated. + +This clearly requires context sensitive analysis. +@dec(x): +def f(): pass +is equivalent to f = dec(x)(f) +but in a context-insensitive context. +Need a method: +Object decoratored_function(Object decorator, Object undecorated); +But what is the decorator and what object is available as a result? +Need to create an object for each decorator of a class or function. +That should be the actual Object. + +There is an assumption that each Object has a one-to-one mapping with a FlowNode +adding extra Objects for decorators might be a problem, since the decorator 'dec' +will point to another object (it could even points to itself if it were a lambda), +yet we need an Object for each level of decorated function. +To do this all decorated function object have the (Function|Class)Expr as + origin. This requires that the getOrigin() method will need refinement for those + QL types. + + + + + diff --git a/python/ql/src/semmle/python/types/ImportTime.qll b/python/ql/src/semmle/python/types/ImportTime.qll new file mode 100644 index 000000000000..0a9e7c9d1457 --- /dev/null +++ b/python/ql/src/semmle/python/types/ImportTime.qll @@ -0,0 +1,35 @@ +import python + +/** An ImportTimeScope is any scope that is not nested within a function and will thus be executed if its + * enclosing module is imported. + * Note however, that if a scope is not an ImportTimeScope it may still be executed at import time. + * This is an artificial approximation, which is necessary for static analysis. + */ +class ImportTimeScope extends Scope { + + ImportTimeScope() { + not this.getEnclosingScope*() instanceof Function + } + + /** Whether this scope explicitly defines 'name'. + * Does not cover implicit definitions be import * */ + pragma[nomagic] + predicate definesName(string name) { + exists(SsaVariable var | name = var.getId() and var.getAUse() = this.getANormalExit()) + } + + /** Holds if the control flow passes from `outer` to `inner` when this scope starts executing */ + predicate entryEdge(ControlFlowNode outer, ControlFlowNode inner) { + inner = this.getEntryNode() and + outer.getNode().(ClassExpr).getInnerScope() = this + } + + /** Gets the global variable that is used during lookup, should `var` be undefined. */ + GlobalVariable getOuterVariable(LocalVariable var) { + this instanceof Class and + var.getScope() = this and + result.getScope() = this.getEnclosingModule() and + var.getId() = result.getId() + } + +} diff --git a/python/ql/src/semmle/python/types/ModuleKind.qll b/python/ql/src/semmle/python/types/ModuleKind.qll new file mode 100644 index 000000000000..abb39626ff10 --- /dev/null +++ b/python/ql/src/semmle/python/types/ModuleKind.qll @@ -0,0 +1,41 @@ +import python + +private predicate is_normal_module(ModuleObject m) { + m instanceof BuiltinModuleObject + or + m instanceof PackageObject + or + exists(ImportingStmt i | m.importedAs(i.getAnImportedModuleName())) + or + m.getName().matches("%\\_\\_init\\_\\_") +} + +private predicate is_script(ModuleObject m) { + not is_normal_module(m) and ( + m.getModule().getFile().getExtension() != ".py" + or + exists(If i, Name name, StrConst main, Cmpop op | + i.getScope() = m.getModule() and + op instanceof Eq and + i.getTest().(Compare).compares(name, op, main) and + name.getId() = "__name__" and main.getText() = "__main__" + ) + ) +} + +private predicate is_plugin(ModuleObject m) { + // This needs refining but is sufficient for our present needs. + not is_normal_module(m) and + not is_script(m) +} + +/** Gets the kind for module `m` will be one of + * "module", "script" or "plugin" + */ +string getKindForModule(ModuleObject m) { + is_normal_module(m) and result = "module" + or + is_script(m) and result = "script" + or + is_plugin(m) and result = "plugin" +} diff --git a/python/ql/src/semmle/python/types/ModuleObject.qll b/python/ql/src/semmle/python/types/ModuleObject.qll new file mode 100644 index 000000000000..6a05cf0afe35 --- /dev/null +++ b/python/ql/src/semmle/python/types/ModuleObject.qll @@ -0,0 +1,259 @@ +import python +private import semmle.python.pointsto.PointsTo +private import semmle.python.pointsto.Base +private import semmle.python.types.ModuleKind + +abstract class ModuleObject extends Object { + + ModuleObject () { + exists(Module m | m.getEntryNode() = this) + or + py_cobjecttypes(this, theModuleType()) + } + + /** Gets the scope corresponding to this module, if this is a Python module */ + Module getModule() { + none() + } + + Container getPath() { + none() + } + + /** Gets the name of this scope */ + abstract string getName(); + + override string toString() { + result = "Module " + this.getName() + } + + /** Gets the named attribute of this module. Using attributeRefersTo() instead + * may provide better results for presentation. */ + pragma [noinline] + abstract Object getAttribute(string name); + + /** Whether the named attribute of this module "refers-to" value, with a known origin. + */ + abstract predicate attributeRefersTo(string name, Object value, ControlFlowNode origin); + + /** Whether the named attribute of this module "refers-to" value, with known class and a known origin. + */ + abstract predicate attributeRefersTo(string name, Object value, ClassObject cls, ControlFlowNode origin); + + /** Gets the package for this module. */ + PackageObject getPackage() { + this.getName().matches("%.%") and + result.getName() = this.getName().regexpReplaceAll("\\.[^.]*$", "") + } + + /** Whether this module "exports" `name`. That is, whether using `import *` on this module + will result in `name` being added to the namespace. */ + predicate exports(string name) { + PointsTo::module_exports(this, name) + } + + /** Whether the complete set of names "exported" by this module can be accurately determined */ + abstract predicate exportsComplete(); + + /** Gets the short name of the module. For example the short name of module x.y.z is 'z' */ + string getShortName() { + result = this.getName().suffix(this.getPackage().getName().length()+1) + or + result = this.getName() and not exists(this.getPackage()) + } + + /** Whether this module is imported by 'import name'. For example on a linux system, + * the module 'posixpath' is imported as 'os.path' or as 'posixpath' */ + predicate importedAs(string name) { + PointsTo::module_imported_as(this, name) + } + + abstract predicate hasAttribute(string name); + + ModuleObject getAnImportedModule() { + result.importedAs(this.getModule().getAnImportedModuleName()) + } + + /** Gets the kind for this module. Will be one of + * "module", "script" or "plugin". + */ + string getKind() { + result = getKindForModule(this) + } + + override boolean booleanValue() { + result = true + } + +} + +class BuiltinModuleObject extends ModuleObject { + + BuiltinModuleObject () { + py_cobjecttypes(this, theModuleType()) + } + + override string getName() { + py_cobjectnames(this, result) + } + + override Object getAttribute(string name) { + py_cmembers_versioned(this, name, result, major_version().toString()) + } + + override predicate hasAttribute(string name) { + py_cmembers_versioned(this, name, _, major_version().toString()) + } + + override predicate attributeRefersTo(string name, Object value, ControlFlowNode origin) { + none() + } + + override predicate attributeRefersTo(string name, Object value, ClassObject cls, ControlFlowNode origin) { + none() + } + + override predicate exportsComplete() { + any() + } + + +} + +class PythonModuleObject extends ModuleObject { + + PythonModuleObject() { + exists(Module m | m.getEntryNode() = this | + not m.isPackage() + ) + } + + override string getName() { + result = this.getModule().getName() + } + + override Module getModule() { + result = this.getOrigin() + } + + override Container getPath() { + result = this.getModule().getFile() + } + + override Object getAttribute(string name) { + this.attributeRefersTo(name, result, _, _) + } + + override predicate exportsComplete() { + exists(Module m | + m = this.getModule() | + not exists(Call modify, Attribute attr, GlobalVariable all | + modify.getScope() = m and modify.getFunc() = attr and + all.getId() = "__all__" | + attr.getObject().(Name).uses(all) + ) + ) + } + + override predicate hasAttribute(string name) { + PointsTo::module_defines_name(this.getModule(), name) + or + this.attributeRefersTo(name, _, _, _) + or + /* The interpreter always adds the __name__ and __package__ attributes */ + name = "__name__" or name = "__package__" + } + + override predicate attributeRefersTo(string name, Object value, ControlFlowNode origin) { + PointsTo::py_module_attributes(this.getModule(), name, value, _, origin) + } + + override predicate attributeRefersTo(string name, Object value, ClassObject cls, ControlFlowNode origin) { + PointsTo::py_module_attributes(this.getModule(), name, value, cls, origin) + } + +} + +/** Primarily for internal use. + * + * Gets the object for the string text. + * The extractor will have populated a str object + * for each module name, with the name b'text' or u'text' (including the quotes). + */ +Object object_for_string(string text) { + py_cobjecttypes(result, theStrType()) and + exists(string repr | + py_cobjectnames(result, repr) and + repr.charAt(1) = "'" | + /* Strip quotes off repr */ + text = repr.substring(2, repr.length()-1) + ) +} + +class PackageObject extends ModuleObject { + + PackageObject() { + exists(Module p | p.getEntryNode() = this | + p.isPackage() + ) + } + + override string getName() { + result = this.getModule().getName() + } + + override Module getModule() { + result = this.getOrigin() + } + + override Container getPath() { + exists(ModuleObject m | + m.getPackage() = this | + result = m.getPath().getParent() + ) + } + + ModuleObject submodule(string name) { + result.getPackage() = this and + name = result.getShortName() + } + + override Object getAttribute(string name) { + PointsTo::package_attribute_points_to(this, name, result, _, _) + } + + PythonModuleObject getInitModule() { + result.getModule() = this.getModule().getInitModule() + } + + override predicate exportsComplete() { + not exists(this.getInitModule()) + or + this.getInitModule().exportsComplete() + } + + override predicate hasAttribute(string name) { + exists(this.submodule(name)) + or + this.getInitModule().hasAttribute(name) + } + + override predicate attributeRefersTo(string name, Object value, ControlFlowNode origin) { + PointsTo::package_attribute_points_to(this, name, value, _, origin) + } + + override predicate attributeRefersTo(string name, Object value, ClassObject cls, ControlFlowNode origin) { + PointsTo::package_attribute_points_to(this, name, value, cls, origin) + } + + Location getLocation() { + none() + } + + override predicate hasLocationInfo(string path, int bl, int bc, int el, int ec) { + path = this.getPath().getName() and + bl = 0 and bc = 0 and el = 0 and ec = 0 + } + +} + diff --git a/python/ql/src/semmle/python/types/Object.qll b/python/ql/src/semmle/python/types/Object.qll new file mode 100644 index 000000000000..10b569ed0767 --- /dev/null +++ b/python/ql/src/semmle/python/types/Object.qll @@ -0,0 +1,518 @@ +import python +private import semmle.python.pointsto.Base + +private cached predicate is_an_object(@py_object obj) { + /* CFG nodes for numeric literals, all of which have a @py_cobject for the value of that literal */ + not obj.(ControlFlowNode).getNode() instanceof ImmutableLiteral + and + not ( + /* @py_cobjects for modules which have a corresponding Python module */ + exists(@py_cobject mod_type | py_special_objects(mod_type, "ModuleType") and py_cobjecttypes(obj, mod_type)) and + exists(Module m | py_cobjectnames(obj, m.getName())) + ) +} + +/** Instances of this class represent objects in the Python program. However, since + * the QL database is static and Python programs are dynamic, there are necessarily a + * number of approximations. + * + * Each point in the control flow graph where a new object can be created is treated as + * an object. Many builtin objects, such as integers, strings and builtin classes, are + * also treated as 'objects'. Hence each 'object', that is an instance of this class, + * represents a set of actual Python objects in the actual program. + * + * Ideally each set would contain only one member, but that is not possible in practice. + * Many instances of this class will represent many actual Python objects, especially + * if the point in the control flow graph to which they refer is in a loop. Others may not + * refer to any objects. However, for many important objects such as classes and functions, + * there is a one-to-one relation. + */ +class Object extends @py_object { + + Object() { + is_an_object(this) + } + + /** Gets an inferred type for this object, without using inter-procedural analysis. + * WARNING: The lack of context makes this less accurate than f.refersTo(this, result, _) + * for a control flow node 'f' */ + ClassObject getAnInferredType() { + exists(ControlFlowNode somewhere | somewhere.refersTo(this, result, _)) + or + py_cobjecttypes(this, result) and not this = unknownValue() + or + this = unknownValue() and result = theUnknownType() + } + + /** Whether this a builtin object. A builtin object is one defined by the implementation, + such as the integer 4 or by a native extension, such as a NumPy array class. */ + predicate isBuiltin() { + py_cobjects(this) + } + + /** Retained for backwards compatibility. See Object.isBuiltin() */ + predicate isC() { + this.isBuiltin() + } + + /** Gets the point in the source code from which this object "originates". + * + * WARNING: The lack of context makes this less accurate than f.refersTo(this, _, result) + * for a control flow node 'f'. + */ + AstNode getOrigin() { + py_flow_bb_node(this, result, _, _) + } + + private predicate hasOrigin() { + py_flow_bb_node(this, _, _, _) + } + + predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { + this.hasOrigin() and this.getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec) + or + not this.hasOrigin() and filepath = ":Compiled Code" and bl = 0 and bc = 0 and el = 0 and ec = 0 + } + + string toString() { + this.isC() and + not this = undefinedVariable() and not this = unknownValue() and + exists(ClassObject type, string typename, string objname | + py_cobjecttypes(this, type) and py_cobjectnames(this, objname) and typename = type.getName() | + result = typename + " " + objname + ) + or + result = this.getOrigin().toString() + } + + /** Gets the class of this object for simple cases, namely constants, functions, + * comprehensions and built-in objects. + * + * This exists primarily for internal use. Use getAnInferredType() instead. + */ + cached ClassObject simpleClass() { + result = comprehension(this.getOrigin()) + or + result = collection_literal(this.getOrigin()) + or + result = string_literal(this.getOrigin()) + or + this.getOrigin() instanceof CallableExpr and result = thePyFunctionType() + or + this.getOrigin() instanceof Module and result = theModuleType() + or + py_cobjecttypes(this, result) + } + + private + ClassObject declaringClass(string name) { + result.declaredAttribute(name) = this + } + + /** Whether this overrides o. In this context, "overrides" means that this object + * is a named attribute of a some class C and `o` is a named attribute of another + * class S, both attributes having the same name, and S is a super class of C. + */ + predicate overrides(Object o) { + exists(string name | + declaringClass(name).getASuperType() = o.declaringClass(name) + ) + } + + /** The Boolean value of this object if it always evaluates to true or false. + * For example: + * false for None, true for 7 and no result for int(x) + */ + boolean booleanValue() { + this = theNoneObject() and result = false + or + this = theTrueObject() and result = true + or + this = theFalseObject() and result = false + or + this = theEmptyTupleObject() and result = false + or + exists(Tuple t | t = this.getOrigin() | + exists(t.getAnElt()) and result = true + or + not exists(t.getAnElt()) and result = false + ) + or + exists(Unicode s | s.getLiteralObject() = this | + s.getS() = "" and result = false + or + s.getS() != "" and result = true + ) + or + exists(Bytes s | s.getLiteralObject() = this | + s.getS() = "" and result = false + or + s.getS() != "" and result = true + ) + } + + /** Holds if this object can be referred to by `longName` + * For example, the modules `dict` in the `sys` module + * has the long name `sys.modules` and the name `os.path.join` + * will refer to the path joining function even though it might + * be declared in the `posix` or `nt` modules. + * Long names can have no more than three dots after the module name. + */ + cached predicate hasLongName(string longName) { + this = findByName0(longName) or + this = findByName1(longName) or + this = findByName2(longName) or + this = findByName3(longName) or + exists(ClassMethodObject cm | + cm.hasLongName(longName) and + cm.getFunction() = this + ) + or + exists(StaticMethodObject cm | + cm.hasLongName(longName) and + cm.getFunction() = this + ) + } + +} + +private Object findByName0(string longName) { + result.(ModuleObject).getName() = longName +} + +private Object findByName1(string longName) { + exists(string owner, string attrname | + longName = owner + "." + attrname + | + result = findByName0(owner).(ModuleObject).getAttribute(attrname) + or + result = findByName0(owner).(ClassObject).lookupAttribute(attrname) + ) + and + not result = findByName0(_) +} + +private Object findByName2(string longName) { + exists(string owner, string attrname | + longName = owner + "." + attrname + | + result = findByName1(owner).(ModuleObject).getAttribute(attrname) + or + result = findByName1(owner).(ClassObject).lookupAttribute(attrname) + ) + and not result = findByName0(_) + and not result = findByName1(_) +} + +private Object findByName3(string longName) { + exists(string owner, string attrname | + longName = owner + "." + attrname + | + result = findByName2(owner).(ModuleObject).getAttribute(attrname) + or + result = findByName2(owner).(ClassObject).lookupAttribute(attrname) + ) + and not result = findByName0(_) + and not result = findByName1(_) + and not result = findByName2(_) +} + + +/** Numeric objects (ints and floats). + * Includes those occurring in the source as a literal + * or in a builtin module as a value. + */ +class NumericObject extends Object { + + NumericObject() { + py_cobjecttypes(this, theIntType()) or + py_cobjecttypes(this, theLongType()) or + py_cobjecttypes(this, theFloatType()) + } + + /** Gets the Boolean value that this object + * would evaluate to in a Boolean context, + * such as `bool(x)` or `if x: ...` + */ + override boolean booleanValue() { + this.intValue() != 0 and result = true + or + this.intValue() = 0 and result = false + or + this.floatValue() != 0 and result = true + or + this.floatValue() = 0 and result = false + } + + /** Gets the value of this object if it is a constant integer and it fits in a QL int */ + int intValue() { + (py_cobjecttypes(this, theIntType()) or py_cobjecttypes(this, theLongType())) + and + exists(string s | py_cobjectnames(this, s) and result = s.toInt()) + } + + /** Gets the value of this object if it is a constant float */ + float floatValue() { + (py_cobjecttypes(this, theFloatType())) + and + exists(string s | py_cobjectnames(this, s) and result = s.toFloat()) + } + + /** Gets the string representation of this object, equivalent to calling repr() in Python */ + string repr() { + exists(string s | + py_cobjectnames(this, s) | + if py_cobjecttypes(this, theLongType()) then + result = s + "L" + else + result = s + ) + } + +} + +/** String objects (unicode or bytes). + * Includes those occurring in the source as a literal + * or in a builtin module as a value. + */ +class StringObject extends Object { + + StringObject() { + py_cobjecttypes(this, theUnicodeType()) or + py_cobjecttypes(this, theBytesType()) + } + + /** Whether this string is composed entirely of ascii encodable characters */ + predicate isAscii() { + this.getText().regexpMatch("^\\p{ASCII}*$") + } + + override boolean booleanValue() { + this.getText() = "" and result = false + or + this.getText() != "" and result = true + } + + /** Gets the text for this string */ + cached string getText() { + exists(string quoted_string | + py_cobjectnames(this, quoted_string) and + result = quoted_string.regexpCapture("[bu]'([\\s\\S]*)'", 1) + ) + } + +} + +/** Sequence objects (lists and tuples) + * Includes those occurring in the source as a literal + * or in a builtin module as a value. + */ +abstract class SequenceObject extends Object { + + /** Gets the length of this sequence */ + int getLength() { + result = strictcount(this.getBuiltinElement(_)) + or + result = strictcount(this.getSourceElement(_)) + } + + /** Gets the nth item of this builtin sequence */ + Object getBuiltinElement(int n) { + py_citems(this, n, result) + } + + /** Gets the nth source element of this sequence */ + ControlFlowNode getSourceElement(int n) { + result = this.(SequenceNode).getElement(n) + } + + Object getInferredElement(int n) { + result = this.getBuiltinElement(n) + or + this.getSourceElement(n).refersTo(result) + } + +} + +class TupleObject extends SequenceObject { + + TupleObject() { + py_cobjecttypes(this, theTupleType()) + or + this instanceof TupleNode + or + exists(Function func | func.getVararg().getAFlowNode() = this) + } + +} + +class NonEmptyTupleObject extends TupleObject { + + NonEmptyTupleObject() { + exists(Function func | func.getVararg().getAFlowNode() = this) + } + + override boolean booleanValue() { + result = true + } + +} + + +class ListObject extends SequenceObject { + + ListObject() { + py_cobjecttypes(this, theListType()) + or + this instanceof ListNode + } + +} + +/** The `builtin` module */ +BuiltinModuleObject theBuiltinModuleObject() { + py_special_objects(result, "builtin_module_2") and major_version() = 2 + or + py_special_objects(result, "builtin_module_3") and major_version() = 3 +} + +/** The `sys` module */ +BuiltinModuleObject theSysModuleObject() { + py_special_objects(result, "sys") +} + +Object builtin_object(string name) { + py_cmembers_versioned(theBuiltinModuleObject(), name, result, major_version().toString()) +} + +/** The built-in object None */ + Object theNoneObject() { + py_special_objects(result, "None") +} + +/** The built-in object True */ + Object theTrueObject() { + py_special_objects(result, "True") +} + +/** The built-in object False */ + Object theFalseObject() { + py_special_objects(result, "False") +} + +/** The builtin function apply (Python 2 only) */ + Object theApplyFunction() { + result = builtin_object("apply") +} + +/** The builtin function hasattr */ + Object theHasattrFunction() { + result = builtin_object("hasattr") +} + +/** The builtin function len */ + Object theLenFunction() { + result = builtin_object("len") +} + +/** The builtin function format */ + Object theFormatFunction() { + result = builtin_object("format") +} + +/** The builtin function open */ + Object theOpenFunction() { + result = builtin_object("open") +} + +/** The builtin function print (Python 2.7 upwards) */ + Object thePrintFunction() { + result = builtin_object("print") +} + +/** The builtin function input (Python 2 only) */ + Object theInputFunction() { + result = builtin_object("input") +} + +/** The builtin function locals */ + Object theLocalsFunction() { + py_special_objects(result, "locals") +} + +/** The builtin function globals */ + Object theGlobalsFunction() { + py_special_objects(result, "globals") +} + +/** The builtin function sys.exit */ + Object theExitFunctionObject() { + py_cmembers_versioned(theSysModuleObject(), "exit", result, major_version().toString()) +} + +/** The NameError class */ +Object theNameErrorType() { + result = builtin_object("NameError") +} + +/** The StandardError class */ +Object theStandardErrorType() { + result = builtin_object("StandardError") +} + +/** The IndexError class */ +Object theIndexErrorType() { + result = builtin_object("IndexError") +} + +/** The LookupError class */ +Object theLookupErrorType() { + result = builtin_object("LookupError") +} + +/** The named quitter object (quit or exit) in the builtin namespace */ +Object quitterObject(string name) { + (name = "quit" or name = "exit") and + result = builtin_object(name) +} + +/** The builtin object `NotImplemented`. Not be confused with `NotImplementedError`. */ +Object theNotImplementedObject() { + result = builtin_object("NotImplemented") +} + +Object theEmptyTupleObject() { + py_cobjecttypes(result, theTupleType()) and not py_citems(result, _, _) +} + + +private ClassObject comprehension(Expr e) { + e instanceof ListComp and result = theListType() + or + e instanceof SetComp and result = theSetType() + or + e instanceof DictComp and result = theDictType() + or + e instanceof GeneratorExp and result = theGeneratorType() +} + +private ClassObject collection_literal(Expr e) { + e instanceof List and result = theListType() + or + e instanceof Set and result = theSetType() + or + e instanceof Dict and result = theDictType() + or + e instanceof Tuple and result = theTupleType() +} + +private ClassObject string_literal(Expr e) { + e instanceof Bytes and result = theBytesType() + or + e instanceof Unicode and result = theUnicodeType() +} + +Object theUnknownType() { + py_special_objects(result, "_semmle_unknown_type") +} + diff --git a/python/ql/src/semmle/python/types/Properties.qll b/python/ql/src/semmle/python/types/Properties.qll new file mode 100644 index 000000000000..01a6e07155de --- /dev/null +++ b/python/ql/src/semmle/python/types/Properties.qll @@ -0,0 +1,144 @@ +import python + +/** A Python property: + * @property + * def f(): + * .... + * + * Also any instances of types.GetSetDescriptorType (which are equivalent, but implemented in C) + */ +abstract class PropertyObject extends Object { + + PropertyObject() { + property_getter(this, _) + or + py_cobjecttypes(this, theBuiltinPropertyType()) + } + + /** Gets the name of this property */ + abstract string getName(); + + /** Gets the getter of this property */ + abstract Object getGetter(); + + /** Gets the setter of this property */ + abstract Object getSetter(); + + /** Gets the deleter of this property */ + abstract Object getDeleter(); + + override string toString() { + result = "Property " + this.getName() + } + + /** Whether this property is read-only. */ + predicate isReadOnly() { + not exists(this.getSetter()) + } + + /** Gets an inferred type of this property. + * That is the type returned by its getter function, + * not the type of the property object which is types.PropertyType. */ + abstract ClassObject getInferredPropertyType(); + +} + + +class PythonPropertyObject extends PropertyObject { + + PythonPropertyObject() { + property_getter(this, _) + } + + override string getName() { + result = this.getGetter().getName() + } + + /** Gets the getter function of this property */ + override FunctionObject getGetter() { + property_getter(this, result) + } + + override ClassObject getInferredPropertyType() { + result = this.getGetter().getAnInferredReturnType() + } + + /** Gets the setter function of this property */ + override FunctionObject getSetter() { + property_setter(this, result) + } + + /** Gets the deleter function of this property */ + override FunctionObject getDeleter() { + property_deleter(this, result) + } + +} + +class BuiltinPropertyObject extends PropertyObject { + + BuiltinPropertyObject() { + py_cobjecttypes(this, theBuiltinPropertyType()) + } + + override string getName() { + py_cobjectnames(this, result) + } + + /** Gets the getter method wrapper of this property */ + override Object getGetter() { + py_cmembers_versioned(this, "__get__", result, major_version().toString()) + } + + override ClassObject getInferredPropertyType() { + none() + } + + /** Gets the setter method wrapper of this property */ + override Object getSetter() { + py_cmembers_versioned(this, "__set__", result, major_version().toString()) + } + + /** Gets the deleter method wrapper of this property */ + override Object getDeleter() { + py_cmembers_versioned(this, "__delete__", result, major_version().toString()) + } + +} + +private predicate property_getter(CallNode decorated, FunctionObject getter) { + decorated.getFunction().refersTo(thePropertyType()) + and + decorated.getArg(0).refersTo(getter) +} + +private predicate property_setter(CallNode decorated, FunctionObject setter) { + property_getter(decorated, _) + and + exists(CallNode setter_call, AttrNode prop_setter | + prop_setter.getObject("setter").refersTo((Object)decorated) | + setter_call.getArg(0).refersTo(setter) + and + setter_call.getFunction() = prop_setter + ) + or + decorated.getFunction().refersTo(thePropertyType()) + and + decorated.getArg(1).refersTo(setter) +} + +private predicate property_deleter(CallNode decorated, FunctionObject deleter) { + property_getter(decorated, _) + and + exists(CallNode deleter_call, AttrNode prop_deleter | + prop_deleter.getObject("deleter").refersTo((Object)decorated) | + deleter_call.getArg(0).refersTo(deleter) + and + deleter_call.getFunction() = prop_deleter + ) + or + decorated.getFunction().refersTo(thePropertyType()) + and + decorated.getArg(2).refersTo(deleter) +} + diff --git a/python/ql/src/semmle/python/types/Version.qll b/python/ql/src/semmle/python/types/Version.qll new file mode 100644 index 000000000000..7d722167a5b8 --- /dev/null +++ b/python/ql/src/semmle/python/types/Version.qll @@ -0,0 +1,169 @@ +import python +import semmle.python.GuardedControlFlow +private import semmle.python.pointsto.PointsTo + +/** A Version of the Python interpreter. + * Currently only 2.7 or 3.x but may include different sets of versions in the future. */ +class Version extends int { + + Version() { + this = 2 or this = 3 + } + + /** Holds if this version (or set of versions) includes the version `major`.`minor` */ + predicate includes(int major, int minor) { + this = 2 and major = 2 and minor = 7 + or + this = 3 and major = 3 and minor in [4..7] + } + +} + +Object theSysVersionInfoTuple() { + py_cmembers_versioned(theSysModuleObject(), "version_info", result, major_version().toString()) +} + +Object theSysHexVersionNumber() { + py_cmembers_versioned(theSysModuleObject(), "hexversion", result, major_version().toString()) +} + +Object theSysVersionString() { + py_cmembers_versioned(theSysModuleObject(), "version", result, major_version().toString()) +} + + +string reversed(Cmpop op) { + op instanceof Lt and result = ">" + or + op instanceof Gt and result = "<" + or + op instanceof GtE and result = "<=" + or + op instanceof LtE and result = ">=" + or + op instanceof Eq and result = "==" + or + op instanceof NotEq and result = "!=" +} + + +/** DEPRECATED: + * A test on the major version of the Python interpreter + * */ +class VersionTest extends @py_flow_node { + + string toString() { + result = "VersionTest" + } + + VersionTest() { + PointsTo::version_const(this, _, _) + } + + predicate isTrue() { + PointsTo::version_const(this, _, true) + } + + AstNode getNode() { + result = this.(ControlFlowNode).getNode() + } + +} + +/** A guard on the major version of the Python interpreter */ +class VersionGuard extends ConditionBlock { + + VersionGuard() { + exists(VersionTest v | + PointsTo::points_to(this.getLastNode(), _, v, _, _) or + PointsTo::points_to(this.getLastNode(), _, _, _, v) + ) + } + + predicate isTrue() { + exists(VersionTest v | + v.isTrue() | + PointsTo::points_to(this.getLastNode(), _, v, _, _) or + PointsTo::points_to(this.getLastNode(), _, _, _, v) + ) + } + +} + +string os_name(StrConst s) { + exists(string t | + t = s.getText() | + t = "Darwin" and result = "darwin" + or + t = "Windows" and result = "win32" + or + t = "Linux" and result = "linux" + or + not t = "Darwin" and not t = "Windows" and not t = "Linux" and result = t + ) +} + +predicate get_platform_name(Expr e) { + exists(Attribute a, Name n | a = e and n = a.getObject() | + n.getId() = "sys" and a.getName() = "platform" + ) + or + exists(Call c, Attribute a, Name n | + c = e and a = c.getFunc() and n = a.getObject() | + a.getName() = "system" and n.getId() = "platform" + ) +} + +predicate os_compare(ControlFlowNode f, string name) { + exists(Compare c, Expr l, Expr r, Cmpop op | + c = f.getNode() and + l = c.getLeft() and + r = c.getComparator(0) and + op = c.getOp(0) | + (op instanceof Eq or op instanceof Is) + and + ( get_platform_name(l) and name = os_name(r) + or + get_platform_name(r) and name = os_name(l) + ) + ) +} + +class OsTest extends @py_flow_node { + + OsTest() { + os_compare(this, _) + } + + string getOs() { + os_compare(this, result) + } + + string toString() { + result = "OsTest" + } + + AstNode getNode() { + result = this.(ControlFlowNode).getNode() + } + +} + + +class OsGuard extends ConditionBlock { + + OsGuard() { + exists(OsTest t | + PointsTo::points_to(this.getLastNode(), _, theBoolType(), t, _) + ) + } + + string getOs() { + exists(OsTest t | + PointsTo::points_to(this.getLastNode(), _, theBoolType(), t, _) and result = t.getOs() + ) + } + +} + + diff --git a/python/ql/src/semmle/python/values/StringAttributes.qll b/python/ql/src/semmle/python/values/StringAttributes.qll new file mode 100644 index 000000000000..86cc29dc5b30 --- /dev/null +++ b/python/ql/src/semmle/python/values/StringAttributes.qll @@ -0,0 +1,90 @@ +import python + +predicate string_attribute_all(ControlFlowNode n, string attr) { + (n.getNode() instanceof Unicode or n.getNode() instanceof Bytes) and attr = "const" + or + exists(Object s | + n.refersTo(s, theBytesType(), _) and attr = "bytes" and + // We are only interested in bytes if they may cause an exception if + // implicitly converted to unicode. ASCII is safe. + not s.(StringObject).isAscii() + ) +} + +predicate tracked_object(ControlFlowNode obj, string attr) { + tracked_object_all(obj, attr) + or + tracked_object_any(obj, attr) +} + +predicate open_file(Object obj) { + obj.(CallNode).getFunction().refersTo(theOpenFunction()) +} + +predicate string_attribute_any(ControlFlowNode n, string attr) { + attr = "user-input" and + exists(Object input | + n.(CallNode).getFunction().refersTo(input) | + if major_version() = 2 then + input = builtin_object("raw_input") + else + input = theInputFunction() + ) + or + attr = "file-input" and + exists(Object fd | n.(CallNode).getFunction().(AttrNode).getObject("read").refersTo(fd) | + open_file(fd) + ) + or + n.refersTo(_, theUnicodeType(), _) and attr = "unicode" +} + +predicate tracked_object_any(ControlFlowNode obj, string attr) { + string_attribute_any(obj, attr) + or + exists(ControlFlowNode other | + tracking_step(other, obj) | + tracked_object_any(other, attr) + ) +} + +predicate tracked_object_all(ControlFlowNode obj, string attr) { + string_attribute_all(obj, attr) + or + forex(ControlFlowNode other | + tracking_step(other, obj) | + tracked_object_all(other, attr) + ) +} + +predicate tracked_call_step(ControlFlowNode ret, ControlFlowNode call) { + exists(FunctionObject func, Return r | + func.getACall() = call and + func.getFunction() = r.getScope() and + r.getValue() = ret.getNode() + ) +} + +ControlFlowNode sequence_for_iterator(ControlFlowNode f) { + exists(For for | f.getNode() = for.getTarget() | + result.getNode() = for.getIter() and + result.getBasicBlock().dominates(f.getBasicBlock()) + ) +} + +pragma [noinline] +private predicate tracking_step(ControlFlowNode src, ControlFlowNode dest) { + src = dest.(BinaryExprNode).getAnOperand() + or + src = dest.(UnaryExprNode).getOperand() + or + src = sequence_for_iterator(dest) + or + src = dest.(AttrNode).getObject() + or + src = dest.(SubscriptNode).getValue() + or + tracked_call_step(src, dest) + or + dest.refersTo((Object)src) +} diff --git a/python/ql/src/semmle/python/web/Http.qll b/python/ql/src/semmle/python/web/Http.qll new file mode 100644 index 000000000000..5789fda7d869 --- /dev/null +++ b/python/ql/src/semmle/python/web/Http.qll @@ -0,0 +1,25 @@ +import python +import semmle.python.security.TaintTracking +import semmle.python.security.strings.External + +/** Generic taint source from a http request */ +abstract class SimpleHttpRequestTaintSource extends TaintSource { + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExternalStringKind + } + +} + +/** Gets an http verb */ +string httpVerb() { + result = "GET" or result = "POST" or + result = "PUT" or result = "PATCH" or + result = "DELETE" or result = "OPTIONS" or + result = "HEAD" +} + +/** Gets an http verb, in lower case */ +string httpVerbLower() { + result = httpVerb().toLowerCase() +} diff --git a/python/ql/src/semmle/python/web/HttpRedirect.qll b/python/ql/src/semmle/python/web/HttpRedirect.qll new file mode 100644 index 000000000000..f3df7cac80d2 --- /dev/null +++ b/python/ql/src/semmle/python/web/HttpRedirect.qll @@ -0,0 +1,8 @@ +import python + +import semmle.python.security.strings.Basic + +import semmle.python.web.django.Redirect +import semmle.python.web.flask.Redirect +import semmle.python.web.tornado.Redirect +import semmle.python.web.pyramid.Redirect diff --git a/python/ql/src/semmle/python/web/HttpRequest.qll b/python/ql/src/semmle/python/web/HttpRequest.qll new file mode 100644 index 000000000000..1566ac645dcd --- /dev/null +++ b/python/ql/src/semmle/python/web/HttpRequest.qll @@ -0,0 +1,5 @@ +import semmle.python.web.django.Request +import semmle.python.web.flask.Request +import semmle.python.web.tornado.Request +import semmle.python.web.pyramid.Request +import semmle.python.web.twisted.Request diff --git a/python/ql/src/semmle/python/web/HttpResponse.qll b/python/ql/src/semmle/python/web/HttpResponse.qll new file mode 100644 index 000000000000..f38836d768b4 --- /dev/null +++ b/python/ql/src/semmle/python/web/HttpResponse.qll @@ -0,0 +1,5 @@ +import semmle.python.web.django.Response +import semmle.python.web.flask.Response +import semmle.python.web.pyramid.Response +import semmle.python.web.tornado.Response +import semmle.python.web.twisted.Response diff --git a/python/ql/src/semmle/python/web/django/Db.qll b/python/ql/src/semmle/python/web/django/Db.qll new file mode 100644 index 000000000000..e51955b154fb --- /dev/null +++ b/python/ql/src/semmle/python/web/django/Db.qll @@ -0,0 +1,65 @@ +import python +import semmle.python.security.injection.Sql + +/** A taint kind representing a django cursor object. + */ +class DjangoDbCursor extends DbCursor { + + DjangoDbCursor() { + this = "django.db.connection.cursor" + } + +} + +private Object theDjangoConnectionObject() { + any(ModuleObject m | m.getName() = "django.db").getAttribute("connection") = result +} + +/** A kind of taint source representing sources of django cursor objects. + */ +class DjangoDbCursorSource extends DbConnectionSource { + + DjangoDbCursorSource() { + exists(AttrNode cursor | + this.(CallNode).getFunction()= cursor and + cursor.getObject("cursor").refersTo(theDjangoConnectionObject()) + ) + } + + override string toString() { + result = "django.db.connection.cursor" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof DjangoDbCursor + } + +} + + +ClassObject theDjangoRawSqlClass() { + result = any(ModuleObject m | m.getName() = "django.db.models.expressions").getAttribute("RawSQL") +} + +/** + * A sink of taint on calls to `django.db.models.expressions.RawSQL`. This + * allows arbitrary SQL statements to be executed, which is a security risk. + */ + +class DjangoRawSqlSink extends TaintSink { + DjangoRawSqlSink() { + exists(CallNode call | + call = theDjangoRawSqlClass().getACall() and + this = call.getArg(0) + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + + override string toString() { + result = "django.db.models.expressions.RawSQL(sink,...)" + } +} + diff --git a/python/ql/src/semmle/python/web/django/Model.qll b/python/ql/src/semmle/python/web/django/Model.qll new file mode 100644 index 000000000000..d3b145e3a46d --- /dev/null +++ b/python/ql/src/semmle/python/web/django/Model.qll @@ -0,0 +1,156 @@ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Basic +import semmle.python.web.Http + +/** A django model class */ +class DjangoModel extends ClassObject { + + DjangoModel() { + any(ModuleObject m | m.getName() = "django.db.models").getAttribute("Model") = this.getAnImproperSuperType() + } + +} + +/** A "taint" for django database tables */ +class DjangoDbTableObjects extends TaintKind { + + DjangoDbTableObjects() { + this = "django.db.models.Model.objects" + } + + override TaintKind getTaintOfMethodResult(string name) { + result = this and + ( + name = "filter" or + name = "exclude" or + name = "annotate" or + name = "order_by" or + name = "reverse" or + name = "distinct" or + name = "values" or + name = "values_list" or + name = "dates" or + name = "datetimes" or + name = "none" or + name = "all" or + name = "union" or + name = "intersection" or + name = "difference" or + name = "select_related" or + name = "prefetch_related" or + name = "extra" or + name = "defer" or + name = "only" or + name = "using" or + name = "select_for_update" or + name = "raw" + ) + } +} + +/** Django model objects, which are sources of django database table "taint" */ +class DjangoModelObjects extends TaintSource { + + DjangoModelObjects() { + this.(AttrNode).isLoad() and this.(AttrNode).getObject("objects").refersTo(any(DjangoModel m)) + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof DjangoDbTableObjects + } + + override string toString() { + result = "django.db.models.Model.objects" + } + +} + +/** A write to a field of a django model, which is a vulnerable to external data. */ +class DjangoModelFieldWrite extends TaintSink { + + DjangoModelFieldWrite() { + exists(AttrNode attr, DjangoModel model | + this = attr and attr.isStore() and attr.getObject(_).refersTo(model) + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + + override string toString() { + result = "django model field write" + } + +} + +/** A direct reference to a django model object, which is a vulnerable to external data. */ +class DjangoModelDirectObjectReference extends TaintSink { + + DjangoModelDirectObjectReference() { + exists(CallNode objects_get_call, ControlFlowNode objects | + this = objects_get_call.getAnArg() | + objects_get_call.getFunction().(AttrNode).getObject("get") = objects and + any(DjangoDbTableObjects objs).taints(objects) + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + + override string toString() { + result = "django model object reference" + } +} + +/** + * A call to the `raw` method on a django model. This allows a raw SQL query + * to be sent to the database, which is a security risk. + */ + +class DjangoModelRawCall extends TaintSink { + + DjangoModelRawCall() { + exists(CallNode raw_call, ControlFlowNode queryset | + this = raw_call.getArg(0) | + raw_call.getFunction().(AttrNode).getObject("raw") = queryset and + any(DjangoDbTableObjects objs).taints(queryset) + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + + override string toString() { + result = "django.models.QuerySet.raw(sink,...)" + } +} + +/** + * A call to the `extra` method on a django model. This allows a raw SQL query + * to be sent to the database, which is a security risk. + */ + + +class DjangoModelExtraCall extends TaintSink { + + DjangoModelExtraCall() { + exists(CallNode extra_call, ControlFlowNode queryset | + this = extra_call.getArg(0) | + extra_call.getFunction().(AttrNode).getObject("extra") = queryset and + any(DjangoDbTableObjects objs).taints(queryset) + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + + override string toString() { + result = "django.models.QuerySet.extra(sink,...)" + } +} diff --git a/python/ql/src/semmle/python/web/django/Redirect.qll b/python/ql/src/semmle/python/web/django/Redirect.qll new file mode 100644 index 000000000000..a78c7a765ecd --- /dev/null +++ b/python/ql/src/semmle/python/web/django/Redirect.qll @@ -0,0 +1,32 @@ +/** Provides class representing the `django.redirect` function. + * This module is intended to be imported into a taint-tracking query + * to extend `TaintSink`. + */ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Basic +private import semmle.python.web.django.Shared + + +/** + * Represents an argument to the `django.redirect` function. + */ +class DjangoRedirect extends TaintSink { + + override string toString() { + result = "django.redirect" + } + + DjangoRedirect() { + exists(CallNode call | + redirect().getACall() = call and + this = call.getAnArg() + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + +} diff --git a/python/ql/src/semmle/python/web/django/Request.qll b/python/ql/src/semmle/python/web/django/Request.qll new file mode 100644 index 000000000000..67b82981fb20 --- /dev/null +++ b/python/ql/src/semmle/python/web/django/Request.qll @@ -0,0 +1,164 @@ +import python +import semmle.python.regex + +import semmle.python.security.TaintTracking +import semmle.python.web.Http + + +/** A django.request.HttpRequest object */ +class DjangoRequest extends TaintKind { + + DjangoRequest() { + this = "django.request.HttpRequest" + } + + override TaintKind getTaintOfAttribute(string name) { + (name = "GET" or name = "POST") and + result instanceof DjangoQueryDict + } + + override TaintKind getTaintOfMethodResult(string name) { + + (name = "body" or name = "path") and + result instanceof ExternalStringKind + } +} + +/* Helper for getTaintForStep() */ +pragma [noinline] +private predicate subscript_taint(SubscriptNode sub, ControlFlowNode obj, TaintKind kind) { + sub.getValue() = obj and + kind instanceof ExternalStringKind +} + +/** A django.request.QueryDict object */ +class DjangoQueryDict extends TaintKind { + + DjangoQueryDict() { + this = "django.http.request.QueryDict" + } + + override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { + this.taints(fromnode) and + subscript_taint(tonode, fromnode, result) + } + + override TaintKind getTaintOfMethodResult(string name) { + name = "get" and result instanceof ExternalStringKind + } + +} + +abstract class DjangoRequestSource extends TaintSource { + + override string toString() { + result = "Django request source" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof DjangoRequest + } + +} + +/** Function based views + * https://docs.djangoproject.com/en/1.11/topics/http/views/ + */ +private class DjangoFunctionBasedViewRequestArgument extends DjangoRequestSource { + + DjangoFunctionBasedViewRequestArgument() { + exists(FunctionObject view | + url_dispatch(_, _, view) and + this = view.getFunction().getArg(0).asName().getAFlowNode() + ) + } + +} + +/** Class based views + * https://docs.djangoproject.com/en/1.11/topics/class-based-views/ + * + */ +private class DjangoView extends ClassObject { + + DjangoView() { + any(ModuleObject m | m.getName() = "django.views.generic").getAttribute("View") = this.getAnImproperSuperType() + } +} + +private FunctionObject djangoViewHttpMethod() { + exists(DjangoView view | + view.lookupAttribute(httpVerbLower()) = result + ) +} + +class DjangoClassBasedViewRequestArgument extends DjangoRequestSource { + + DjangoClassBasedViewRequestArgument() { + this = djangoViewHttpMethod().getFunction().getArg(1).asName().getAFlowNode() + } + +} + + + + +/* *********** Routing ********* */ + + +/* Function based views */ +predicate url_dispatch(CallNode call, ControlFlowNode regex, FunctionObject view) { + exists(FunctionObject url | + any(ModuleObject m | m.getName() = "django.conf.urls").getAttribute("url") = url and + url.getArgumentForCall(call, 0) = regex and + url.getArgumentForCall(call, 1).refersTo(view) + ) +} + + +class UrlRegex extends RegexString { + + UrlRegex() { + url_dispatch(_, this.getAFlowNode(), _) + } + +} + +class UrlRouting extends CallNode { + + UrlRouting() { + url_dispatch(this, _, _) + } + + FunctionObject getViewFunction() { + url_dispatch(this, _, result) + } + + string getNamedArgument() { + exists(UrlRegex regex | + url_dispatch(this, regex.getAFlowNode(), _) and + regex.getGroupName(_, _) = result + ) + } + +} + +/** An argument specified in a url routing table */ +class HttpRequestParameter extends TaintSource { + + HttpRequestParameter() { + exists(UrlRouting url | + this.(ControlFlowNode).getNode() = + url.getViewFunction().getFunction().getArgByName(url.getNamedArgument()) + ) + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExternalStringKind + } + + override string toString() { + result = "django.http.request.parameter" + } +} + diff --git a/python/ql/src/semmle/python/web/django/Response.qll b/python/ql/src/semmle/python/web/django/Response.qll new file mode 100644 index 000000000000..ed3833c02799 --- /dev/null +++ b/python/ql/src/semmle/python/web/django/Response.qll @@ -0,0 +1,86 @@ +import python +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Basic +private import semmle.python.web.django.Shared + + +/** A django.http.response.Response object + * This isn't really a "taint", but we use the value tracking machinery to + * track the flow of response objects. + */ +class DjangoResponse extends TaintKind { + + DjangoResponse() { + this = "django.response.HttpResponse" + } + +} + +private ClassObject theDjangoHttpResponseClass() { + result = any(ModuleObject m | m.getName() = "django.http.response").getAttribute("HttpResponse") and + not result = theDjangoHttpRedirectClass() +} + +/** Instantiation of a django response. */ +class DjangoResponseSource extends TaintSource { + + DjangoResponseSource() { + exists(ClassObject cls | + cls.getAnImproperSuperType() = theDjangoHttpResponseClass() and + cls.getACall() = this + ) + } + + override predicate isSourceOf(TaintKind kind) { kind instanceof DjangoResponse } + + override string toString() { + result = "django.http.response.HttpResponse" + } +} + +/** A write to a django response, which is vulnerable to external data (xss) */ +class DjangoResponseWrite extends TaintSink { + + DjangoResponseWrite() { + exists(AttrNode meth, CallNode call | + call.getFunction() = meth and + any(DjangoResponse repsonse).taints(meth.getObject("write")) and + this = call.getArg(0) + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + + override string toString() { + result = "django.Response.write(...)" + } + +} + +/** An argument to initialization of a django response, which is vulnerable to external data (xss) */ +class DjangoResponseContent extends TaintSink { + + DjangoResponseContent() { + exists(CallNode call, ClassObject cls | + cls.getAnImproperSuperType() = theDjangoHttpResponseClass() and + call.getFunction().refersTo(cls) | + call.getArg(0) = this + or + call.getArgByName("content") = this + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + + override string toString() { + result = "django.Response(...)" + } + +} + + + diff --git a/python/ql/src/semmle/python/web/django/Sanitizers.qll b/python/ql/src/semmle/python/web/django/Sanitizers.qll new file mode 100644 index 000000000000..db7f8aff8f86 --- /dev/null +++ b/python/ql/src/semmle/python/web/django/Sanitizers.qll @@ -0,0 +1,7 @@ +import python + + +/* Sanitizers + * No django sanitizers implemented yet. + */ + diff --git a/python/ql/src/semmle/python/web/django/Shared.qll b/python/ql/src/semmle/python/web/django/Shared.qll new file mode 100644 index 000000000000..395c09ec958f --- /dev/null +++ b/python/ql/src/semmle/python/web/django/Shared.qll @@ -0,0 +1,9 @@ +import python + +FunctionObject redirect() { + result = any(ModuleObject m | m.getName() = "django.shortcuts").getAttribute("redirect") +} + +ClassObject theDjangoHttpRedirectClass() { + result = any(ModuleObject m | m.getName() = "django.http.response").getAttribute("HttpResponseRedirectBase") +} diff --git a/python/ql/src/semmle/python/web/flask/General.qll b/python/ql/src/semmle/python/web/flask/General.qll new file mode 100644 index 000000000000..febe7372b9b9 --- /dev/null +++ b/python/ql/src/semmle/python/web/flask/General.qll @@ -0,0 +1,117 @@ +import python +import semmle.python.web.Http + +/** The flask module */ +ModuleObject theFlaskModule() { + result = any(ModuleObject m | m.getName() = "flask") +} + +/** The flask app class */ +ClassObject theFlaskClass() { + result = theFlaskModule().getAttribute("Flask") +} + +/** The flask MethodView class */ +ClassObject theFlaskMethodViewClass() { + result = any(ModuleObject m | m.getName() = "flask.views").getAttribute("MethodView") +} + +ClassObject theFlaskReponseClass() { + result = theFlaskModule().getAttribute("Response") +} + +/** Holds if `route` is routed to `func` + * by decorating `func` with `app.route(route)` + */ +predicate app_route(ControlFlowNode route, Function func) { + exists(CallNode route_call, CallNode decorator_call | + route_call.getFunction().(AttrNode).getObject("route").refersTo(_, theFlaskClass(), _) and + decorator_call.getFunction() = route_call and + route_call.getArg(0) = route and + decorator_call.getArg(0).getNode().(FunctionExpr).getInnerScope() = func + ) +} + +/* Helper for add_url_rule */ +private predicate add_url_rule_call(ControlFlowNode regex, ControlFlowNode callable) { + exists(CallNode call | + call.getFunction().(AttrNode).getObject("add_url_rule").refersTo(_, theFlaskClass(), _) and + regex = call.getArg(0) | + callable = call.getArg(2) or + callable = call.getArgByName("view_func") + ) +} + +/** Holds if urls matching `regex` are routed to `func` */ +predicate add_url_rule(ControlFlowNode regex, Function func) { + exists(ControlFlowNode callable | + add_url_rule_call(regex, callable) + | + exists(PyFunctionObject f | f.getFunction() = func and callable.refersTo(f)) + or + /* MethodView.as_view() */ + exists(MethodViewClass view_cls | + view_cls.asTaint().taints(callable) | + func = view_cls.lookupAttribute(httpVerbLower()).(FunctionObject).getFunction() + ) + /* TO DO -- Handle Views that aren't MethodViews */ + ) +} + +/** Holds if urls matching `regex` are routed to `func` using + * any of flask's routing mechanisms. + */ +predicate flask_routing(ControlFlowNode regex, Function func) { + app_route(regex, func) + or + add_url_rule(regex, func) +} + +/** A class that extends flask.views.MethodView */ +private class MethodViewClass extends ClassObject { + + MethodViewClass() { + this.getAnImproperSuperType() = theFlaskMethodViewClass() + } + + /* As we are restricted to strings for taint kinds, we need to map these classes to strings. */ + string taintString() { + result = "flask/" + this.getQualifiedName() + ".as.view" + } + + /* As we are restricted to strings for taint kinds, we need to map these classes to strings. */ + TaintKind asTaint() { + result = this.taintString() + } +} + +private class MethodViewTaint extends TaintKind { + + MethodViewTaint() { + any(MethodViewClass cls).taintString() = this + } +} + +/** A source of method view "taint"s. */ +private class AsView extends TaintSource { + + AsView() { + exists(ClassObject view_class | + view_class.getAnImproperSuperType() = theFlaskMethodViewClass() and + this.(CallNode).getFunction().(AttrNode).getObject("as_view").refersTo(view_class) + ) + } + + override string toString() { + result = "flask.MethodView.as_view()" + } + + override predicate isSourceOf(TaintKind kind) { + exists(MethodViewClass view_class | + kind = view_class.asTaint() and + this.(CallNode).getFunction().(AttrNode).getObject("as_view").refersTo(view_class) + ) + } + +} + diff --git a/python/ql/src/semmle/python/web/flask/Redirect.qll b/python/ql/src/semmle/python/web/flask/Redirect.qll new file mode 100644 index 000000000000..81a6d4dc0645 --- /dev/null +++ b/python/ql/src/semmle/python/web/flask/Redirect.qll @@ -0,0 +1,35 @@ +/** Provides class representing the `flask.redirect` function. + * This module is intended to be imported into a taint-tracking query + * to extend `TaintSink`. + */ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Basic +import semmle.python.web.flask.General + +FunctionObject flask_redirect() { + result = theFlaskModule().getAttribute("redirect") +} + +/** + * Represents an argument to the `flask.redirect` function. + */ +class FlaskRedirect extends TaintSink { + + override string toString() { + result = "flask.redirect" + } + + FlaskRedirect() { + exists(CallNode call | + flask_redirect().getACall() = call and + this = call.getAnArg() + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + +} diff --git a/python/ql/src/semmle/python/web/flask/Request.qll b/python/ql/src/semmle/python/web/flask/Request.qll new file mode 100644 index 000000000000..caa388e0d77a --- /dev/null +++ b/python/ql/src/semmle/python/web/flask/Request.qll @@ -0,0 +1,75 @@ +import python + +import semmle.python.security.TaintTracking +import semmle.python.web.Http +import semmle.python.web.flask.General + +private Object theFlaskRequestObject() { + result = theFlaskModule().getAttribute("request") + +} + +/** Holds if `attr` is an access of attribute `name` of the flask request object */ +private predicate flask_request_attr(AttrNode attr, string name) { + attr.isLoad() and + attr.getObject(name).refersTo(theFlaskRequestObject()) +} + +/** Source of external data from a flask request */ +class FlaskRequestData extends SimpleHttpRequestTaintSource { + + FlaskRequestData() { + not this instanceof FlaskRequestArgs and + exists(string name | + flask_request_attr(this, name) | + name = "path" or name = "full_path" or + name = "base_url" or name = "url" + ) + } + + override string toString() { + result = "flask.request" + } + +} + +/** Source of dictionary whose values are externally controlled */ +class FlaskRequestArgs extends TaintSource { + + FlaskRequestArgs() { + exists(string attr | + flask_request_attr(this, attr) | + attr = "args" or attr = "form" or + attr = "values" or attr = "files" or + attr = "headers" or attr = "json" + ) + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExternalStringDictKind + } + + override string toString() { + result = "flask.request.args" + } + +} + + +/** Source of dictionary whose values are externally controlled */ +class FlaskRequestJson extends TaintSource { + + FlaskRequestJson() { + flask_request_attr(this, "json") + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExternalJsonKind + } + + override string toString() { + result = "flask.request.json" + } + +} + diff --git a/python/ql/src/semmle/python/web/flask/Response.qll b/python/ql/src/semmle/python/web/flask/Response.qll new file mode 100644 index 000000000000..13f51f6519b8 --- /dev/null +++ b/python/ql/src/semmle/python/web/flask/Response.qll @@ -0,0 +1,48 @@ +import python + + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Basic + +import semmle.python.web.flask.General + +/** A flask response, which is vulnerable to any sort of + * http response malice. */ +class FlaskRoutedResponse extends TaintSink { + + FlaskRoutedResponse() { + exists(PyFunctionObject response | + flask_routing(_, response.getFunction()) and + this = response.getAReturnedNode() + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + + override string toString() { + result = "flask.routed.response" + } + +} + + +class FlaskResponseArgument extends TaintSink { + + FlaskResponseArgument() { + exists(CallNode call | + call.getFunction().refersTo(theFlaskReponseClass()) and + call.getArg(0) = this + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + + override string toString() { + result = "flask.response.argument" + } + +} \ No newline at end of file diff --git a/python/ql/src/semmle/python/web/pyramid/Redirect.qll b/python/ql/src/semmle/python/web/pyramid/Redirect.qll new file mode 100644 index 000000000000..61f662232b47 --- /dev/null +++ b/python/ql/src/semmle/python/web/pyramid/Redirect.qll @@ -0,0 +1,42 @@ +/** Provides class representing the `pyramid.redirect` function. + * This module is intended to be imported into a taint-tracking query + * to extend `TaintSink`. + */ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Basic + +private ClassObject redirectClass() { + exists(ModuleObject ex | + ex.getName() = "pyramid.httpexceptions" | + ex.getAttribute("HTTPFound") = result + or + ex.getAttribute("HTTPTemporaryRedirect") = result + ) +} + +/** + * Represents an argument to the `tornado.redirect` function. + */ +class PyramidRedirect extends TaintSink { + + override string toString() { + result = "pyramid.redirect" + } + + PyramidRedirect() { + exists(CallNode call | + call.getFunction().refersTo(redirectClass()) + | + call.getArg(0) = this + or + call.getArgByName("location") = this + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + +} diff --git a/python/ql/src/semmle/python/web/pyramid/Request.qll b/python/ql/src/semmle/python/web/pyramid/Request.qll new file mode 100644 index 000000000000..a35c21203532 --- /dev/null +++ b/python/ql/src/semmle/python/web/pyramid/Request.qll @@ -0,0 +1,39 @@ +import python + +import semmle.python.security.TaintTracking +import semmle.python.web.Http +private import semmle.python.web.webob.Request +private import semmle.python.web.pyramid.View + +class PyramidRequest extends BaseWebobRequest { + + PyramidRequest() { + this = "pyramid.request" + } + + override ClassObject getClass() { + result = any(ModuleObject m | m.getName() = "pyramid.request").getAttribute("Request") + } + +} + +/** Source of pyramid request objects */ +class PyramidViewArgument extends TaintSource { + + PyramidViewArgument() { + exists(Function view_func | + is_pyramid_view_function(view_func) and + this.(ControlFlowNode).getNode() = view_func.getArg(0) + ) + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof PyramidRequest + } + + override string toString() { + result = "pyramid.view.argument" + } + +} + diff --git a/python/ql/src/semmle/python/web/pyramid/Response.qll b/python/ql/src/semmle/python/web/pyramid/Response.qll new file mode 100644 index 000000000000..85f572c9eee9 --- /dev/null +++ b/python/ql/src/semmle/python/web/pyramid/Response.qll @@ -0,0 +1,28 @@ +import python + + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Basic + +private import semmle.python.web.pyramid.View + +/** A pyramid response, which is vulnerable to any sort of + * http response malice. */ +class PyramidRoutedResponse extends TaintSink { + + PyramidRoutedResponse() { + exists(PyFunctionObject view | + is_pyramid_view_function(view.getFunction()) and + this = view.getAReturnedNode() + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + + override string toString() { + result = "pyramid.routed.response" + } + +} diff --git a/python/ql/src/semmle/python/web/pyramid/View.qll b/python/ql/src/semmle/python/web/pyramid/View.qll new file mode 100644 index 000000000000..20e59c4c76d5 --- /dev/null +++ b/python/ql/src/semmle/python/web/pyramid/View.qll @@ -0,0 +1,14 @@ +import python + +ModuleObject thePyramidViewModule() { + result.getName() = "pyramid.view" +} + +Object thePyramidViewConfig() { + result = thePyramidViewModule().getAttribute("view_config") +} + +predicate is_pyramid_view_function(Function func) { + func.getADecorator().refersTo(_, thePyramidViewConfig(), _) +} + diff --git a/python/ql/src/semmle/python/web/tornado/Redirect.qll b/python/ql/src/semmle/python/web/tornado/Redirect.qll new file mode 100644 index 000000000000..3bfd022df729 --- /dev/null +++ b/python/ql/src/semmle/python/web/tornado/Redirect.qll @@ -0,0 +1,33 @@ +/** Provides class representing the `tornado.redirect` function. + * This module is intended to be imported into a taint-tracking query + * to extend `TaintSink`. + */ +import python + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Basic +import Tornado + + +/** + * Represents an argument to the `tornado.redirect` function. + */ +class TornadoRedirect extends TaintSink { + + override string toString() { + result = "tornado.redirect" + } + + TornadoRedirect() { + exists(CallNode call, ControlFlowNode node | + node = call.getFunction().(AttrNode).getObject("redirect") and + isTornadoRequestHandlerInstance(node) and + this = call.getAnArg() + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + +} diff --git a/python/ql/src/semmle/python/web/tornado/Request.qll b/python/ql/src/semmle/python/web/tornado/Request.qll new file mode 100644 index 000000000000..bc28dba114d4 --- /dev/null +++ b/python/ql/src/semmle/python/web/tornado/Request.qll @@ -0,0 +1,93 @@ +import python + +import semmle.python.security.TaintTracking +import semmle.python.web.Http +import Tornado + +/** A tornado.request.HttpRequest object */ +class TornadoRequest extends TaintKind { + + TornadoRequest() { + this = "tornado.request.HttpRequest" + } + + override TaintKind getTaintOfAttribute(string name) { + result instanceof ExternalStringDictKind and + ( + name = "headers" or + name = "arguments" or + name = "cookies" + ) + or + result instanceof ExternalStringKind and + ( + name = "path" or + name = "query" or + name = "body" + ) + } + +} + + +class TornadoRequestSource extends TaintSource { + + TornadoRequestSource() { + isTornadoRequestHandlerInstance(this.(AttrNode).getObject("request")) + } + + override string toString() { + result = "Tornado request source" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof TornadoRequest + } + +} + +class TornadoExternalInputSource extends TaintSource { + + TornadoExternalInputSource() { + exists(string name | + name = "get_argument" or + name = "get_query_argument" or + name = "get_body_argument" or + name = "decode_argument" + | + this = callToNamedTornadoRequestHandlerMethod(name) + ) + } + + override string toString() { + result = "Tornado request method" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExternalStringKind + } + +} + +class TornadoExternalInputListSource extends TaintSource { + + TornadoExternalInputListSource() { + exists(string name | + name = "get_arguments" or + name = "get_query_arguments" or + name = "get_body_arguments" + | + this = callToNamedTornadoRequestHandlerMethod(name) + ) + } + + override string toString() { + result = "Tornado request method" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExternalStringSequenceKind + } + +} + diff --git a/python/ql/src/semmle/python/web/tornado/Response.qll b/python/ql/src/semmle/python/web/tornado/Response.qll new file mode 100644 index 000000000000..242ea816082d --- /dev/null +++ b/python/ql/src/semmle/python/web/tornado/Response.qll @@ -0,0 +1,96 @@ +import python + + +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Basic + +import Tornado + +class TornadoConnection extends TaintKind { + + TornadoConnection() { + this = "tornado.http.connection" + } + +} + +class TornadoConnectionSource extends TaintSource { + + TornadoConnectionSource() { + isTornadoRequestHandlerInstance(this.(AttrNode).getObject("connection")) + } + + override string toString() { + result = "Tornado http connection source" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof TornadoConnection + } + +} + +class TornadoConnectionWrite extends TaintSink { + + override string toString() { + result = "tornado.connection.write" + } + + TornadoConnectionWrite() { + exists(CallNode call, ControlFlowNode conn | + conn = call.getFunction().(AttrNode).getObject("write") and + this = call.getAnArg() | + exists(TornadoConnection tc | tc.taints(conn)) + or + isTornadoRequestHandlerInstance(conn) + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + +} + +class TornadoHttpRequestHandlerWrite extends TaintSink { + + override string toString() { + result = "tornado.HttpRequesHandler.write" + } + + TornadoHttpRequestHandlerWrite() { + exists(CallNode call, ControlFlowNode node | + node = call.getFunction().(AttrNode).getObject("write") and + isTornadoRequestHandlerInstance(node) and + this = call.getAnArg() + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + +} + +class TornadoHttpRequestHandlerRedirect extends TaintSink { + + override string toString() { + result = "tornado.HttpRequesHandler.redirect" + } + + TornadoHttpRequestHandlerRedirect() { + exists(CallNode call, ControlFlowNode node | + node = call.getFunction().(AttrNode).getObject("redirect") and + isTornadoRequestHandlerInstance(node) and + this = call.getArg(0) + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof StringKind + } + +} + + + diff --git a/python/ql/src/semmle/python/web/tornado/Tornado.qll b/python/ql/src/semmle/python/web/tornado/Tornado.qll new file mode 100644 index 000000000000..d20e81953a01 --- /dev/null +++ b/python/ql/src/semmle/python/web/tornado/Tornado.qll @@ -0,0 +1,35 @@ +import python + +import semmle.python.security.TaintTracking + +private ClassObject theTornadoRequestHandlerClass() { + result = any(ModuleObject m | m.getName() = "tornado.web").getAttribute("RequestHandler") +} + +ClassObject aTornadoRequestHandlerClass() { + result.getASuperType() = theTornadoRequestHandlerClass() +} + +FunctionObject getTornadoRequestHandlerMethod(string name) { + result = theTornadoRequestHandlerClass().declaredAttribute(name) +} + +/** Holds if `node` is likely to refer to an instance of a tornado + * `RequestHandler` class. + */ + +predicate isTornadoRequestHandlerInstance(ControlFlowNode node) { + node.refersTo(_, aTornadoRequestHandlerClass(), _) + or + /* In some cases, the points-to analysis won't capture all instances we care + * about. For these, we use the following syntactic check. First, that + * `node` appears inside a method of a subclass of + * `tornado.web.RequestHandler`:*/ + node.getScope().getEnclosingScope().(Class).getClassObject() = aTornadoRequestHandlerClass() and + /* Secondly, that `node` refers to the `self` argument: */ + node.isLoad() and node.(NameNode).isSelf() +} + +CallNode callToNamedTornadoRequestHandlerMethod(string name) { + isTornadoRequestHandlerInstance(result.getFunction().(AttrNode).getObject(name)) +} \ No newline at end of file diff --git a/python/ql/src/semmle/python/web/twisted/Request.qll b/python/ql/src/semmle/python/web/twisted/Request.qll new file mode 100644 index 000000000000..8be5db7bb4d5 --- /dev/null +++ b/python/ql/src/semmle/python/web/twisted/Request.qll @@ -0,0 +1,54 @@ +import python + +import semmle.python.security.TaintTracking +import semmle.python.web.Http +import Twisted + +/** A twisted.web.http.Request object */ +class TwistedRequest extends TaintKind { + + TwistedRequest() { + this = "twisted.request.http.Request" + } + + override TaintKind getTaintOfAttribute(string name) { + result instanceof ExternalStringSequenceDictKind and + ( + name = "args" + ) + or + result instanceof ExternalStringKind and + ( + name = "uri" or + name = "path" + ) + } + + override TaintKind getTaintOfMethodResult(string name) { + ( + name = "getHeader" or + name = "getCookie" or + name = "getUser" or + name = "getPassword" + ) and + result instanceof ExternalStringKind + } + +} + + +class TwistedRequestSource extends TaintSource { + + TwistedRequestSource() { + isTwistedRequestInstance(this) + } + + override string toString() { + result = "Twisted request source" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof TwistedRequest + } + +} diff --git a/python/ql/src/semmle/python/web/twisted/Response.qll b/python/ql/src/semmle/python/web/twisted/Response.qll new file mode 100644 index 000000000000..45c5ad56b35d --- /dev/null +++ b/python/ql/src/semmle/python/web/twisted/Response.qll @@ -0,0 +1,54 @@ +import python + +import semmle.python.security.TaintTracking +import semmle.python.web.Http +import semmle.python.security.strings.Basic +import Twisted +import Request + +class TwistedResponse extends TaintSink { + TwistedResponse() { + exists(PyFunctionObject func, string name | + isKnownRequestHandlerMethodName(name) and + name = func.getName() and + func = getTwistedRequestHandlerMethod(name) and + this = func.getAReturnedNode() + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + + override string toString() { + result = "Twisted response" + } +} + +/** + * A sink of taint in the form of a "setter" method on a twisted request + * object, which affects the properties of the subsequent response sent to this + * request. + */ + class TwistedRequestSetter extends TaintSink { + TwistedRequestSetter() { + exists(CallNode call, ControlFlowNode node, string name | + ( + name = "setHeader" or + name = "addCookie" or + name = "write" + ) and + any(TwistedRequest t).taints(node) and + node = call.getFunction().(AttrNode).getObject(name) and + this = call.getAnArg() + ) + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + + override string toString() { + result = "Twisted request setter" + } +} \ No newline at end of file diff --git a/python/ql/src/semmle/python/web/twisted/Twisted.qll b/python/ql/src/semmle/python/web/twisted/Twisted.qll new file mode 100644 index 000000000000..13db1dc9a8eb --- /dev/null +++ b/python/ql/src/semmle/python/web/twisted/Twisted.qll @@ -0,0 +1,52 @@ +import python + +import semmle.python.security.TaintTracking + +private ClassObject theTwistedHttpRequestClass() { + result = any(ModuleObject m | m.getName() = "twisted.web.http").getAttribute("Request") +} + +private ClassObject theTwistedHttpResourceClass() { + result = any(ModuleObject m | m.getName() = "twisted.web.resource").getAttribute("Resource") +} + +ClassObject aTwistedRequestHandlerClass() { + result.getASuperType() = theTwistedHttpResourceClass() +} + +FunctionObject getTwistedRequestHandlerMethod(string name) { + result = aTwistedRequestHandlerClass().declaredAttribute(name) +} + +bindingset[name] +predicate isKnownRequestHandlerMethodName(string name) { + name = "render" or + name.matches("render_%") +} + +/** Holds if `node` is likely to refer to an instance of the twisted + * `Request` class. + */ +predicate isTwistedRequestInstance(NameNode node) { + node.refersTo(_, theTwistedHttpRequestClass(), _) + or + /* In points-to analysis cannot infer that a given object is an instance of + * the `twisted.web.http.Request` class, we also include any parameter + * called `request` that appears inside a subclass of a request handler + * class, and the appropriate arguments of known request handler methods. + */ + exists(Function func | func = node.getScope() | + func.getEnclosingScope().(Class).getClassObject() = aTwistedRequestHandlerClass() + ) and + ( + /* Any parameter called `request` */ + node.getId() = "request" and + node.isParameter() + or + /* Any request parameter of a known request handler method */ + exists(FunctionObject func | node.getScope() = func.getFunction() | + isKnownRequestHandlerMethodName(func.getName()) and + node.getNode() = func.getFunction().getArg(1) + ) + ) +} diff --git a/python/ql/src/semmle/python/web/webob/Request.qll b/python/ql/src/semmle/python/web/webob/Request.qll new file mode 100644 index 000000000000..5cb11ba23a61 --- /dev/null +++ b/python/ql/src/semmle/python/web/webob/Request.qll @@ -0,0 +1,51 @@ +import python + +import semmle.python.security.TaintTracking +import semmle.python.web.Http + +abstract class BaseWebobRequest extends TaintKind { + + bindingset[this] + BaseWebobRequest() { any() } + + override TaintKind getTaintOfAttribute(string name) { + result instanceof ExternalStringDictKind and + ( + name = "GET" or + name = "POST" or + name = "headers" + ) + or + result instanceof ExternalStringKind and + ( + name = "body" + ) + } + + override TaintKind getTaintOfMethodResult(string name) { + result = this and + ( + name = "copy" or + name = "copy_get" or + name = "copy_body" + ) + or + result instanceof ExternalStringKind and + ( + name = "as_bytes" + ) + } + +} + +class WebobRequest extends BaseWebobRequest { + + WebobRequest() { + this = "webob.Request" + } + + override ClassObject getClass() { + result = any(ModuleObject m | m.getName() = "webob.request").getAttribute("Request") + } + +} diff --git a/python/ql/src/semmle/python/xml/XML.qll b/python/ql/src/semmle/python/xml/XML.qll new file mode 100755 index 000000000000..13cc8d84f371 --- /dev/null +++ b/python/ql/src/semmle/python/xml/XML.qll @@ -0,0 +1,282 @@ +/** + * A library for working with XML files and their content. + */ + +import semmle.python.Files + +/** An XML element that has a location. */ +abstract class XMLLocatable extends @xmllocatable { + /** The source location for this element. */ + Location getLocation() { xmllocations(this,result) } + + /** + * Whether this element has the specified location information, + * including file path, start line, start column, end line and end column. + */ + predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + exists(File f, Location l | l = this.getLocation() | + locations_default(l,f,startline,startcolumn,endline,endcolumn) and + filepath = f.getName() + ) + } + + /** A printable representation of this element. */ + abstract string toString(); +} + +/** + * An `XMLParent` is either an `XMLElement` or an `XMLFile`, + * both of which can contain other elements. + */ +class XMLParent extends @xmlparent { + /** + * A printable representation of this XML parent. + * (Intended to be overridden in subclasses.) + */ + /*abstract*/ string getName() { result = "parent" } + + /** The file to which this XML parent belongs. */ + XMLFile getFile() { result = this or xmlElements(this,_,_,_,result) } + + /** The child element at a specified index of this XML parent. */ + XMLElement getChild(int index) { xmlElements(result, _, this, index, _) } + + /** A child element of this XML parent. */ + XMLElement getAChild() { xmlElements(result,_,this,_,_) } + + /** A child element of this XML parent with the given `name`. */ + XMLElement getAChild(string name) { xmlElements(result,_,this,_,_) and result.hasName(name) } + + /** A comment that is a child of this XML parent. */ + XMLComment getAComment() { xmlComments(result,_,this,_) } + + /** A character sequence that is a child of this XML parent. */ + XMLCharacters getACharactersSet() { xmlChars(result,_,this,_,_,_) } + + /** The depth in the tree. (Overridden in XMLElement.) */ + int getDepth() { result = 0 } + + /** The number of child XML elements of this XML parent. */ + int getNumberOfChildren() { + result = count(XMLElement e | xmlElements(e,_,this,_,_)) + } + + /** The number of places in the body of this XML parent where text occurs. */ + int getNumberOfCharacterSets() { + result = count(int pos | xmlChars(_,_,this,pos,_,_)) + } + + /** + * Append the character sequences of this XML parent from left to right, separated by a space, + * up to a specified (zero-based) index. + */ + string charsSetUpTo(int n) { + (n = 0 and xmlChars(_,result,this,0,_,_)) or + (n > 0 and exists(string chars | xmlChars(_,chars,this,n,_,_) | + result = this.charsSetUpTo(n-1) + " " + chars)) + } + + /** Append all the character sequences of this XML parent from left to right, separated by a space. */ + string allCharactersString() { + exists(int n | n = this.getNumberOfCharacterSets() | + (n = 0 and result = "") or + (n > 0 and result = this.charsSetUpTo(n-1)) + ) + } + + /** The text value contained in this XML parent. */ + string getTextValue() { + result = allCharactersString() + } + + /** A printable representation of this XML parent. */ + string toString() { result = this.getName() } +} + +/** An XML file. */ +class XMLFile extends XMLParent, File { + XMLFile() { + xmlEncoding(this,_) + } + + /** A printable representation of this XML file. */ + override + string toString() { result = XMLParent.super.toString() } + + /** The name of this XML file. */ + override + string getName() { files(this,result,_,_,_) } + + /** The path of this XML file. */ + string getPath() { files(this,_,result,_,_) } + + /** The path of the folder that contains this XML file. */ + string getFolder() { + result = this.getPath().substring(0, this.getPath().length()-this.getName().length()) + } + + /** The encoding of this XML file. */ + string getEncoding() { xmlEncoding(this,result) } + + /** The XML file itself. */ + override + XMLFile getFile() { result = this } + + /** A top-most element in an XML file. */ + XMLElement getARootElement() { result = this.getAChild() } + + /** A DTD associated with this XML file. */ + XMLDTD getADTD() { xmlDTDs(result,_,_,_,this) } +} + +/** A "Document Type Definition" of an XML file. */ +class XMLDTD extends @xmldtd { + /** The name of the root element of this DTD. */ + string getRoot() { xmlDTDs(this,result,_,_,_) } + + /** The public ID of this DTD. */ + string getPublicId() { xmlDTDs(this,_,result,_,_) } + + /** The system ID of this DTD. */ + string getSystemId() { xmlDTDs(this,_,_,result,_) } + + /** Whether this DTD is public. */ + predicate isPublic() { not xmlDTDs(this,_,"",_,_) } + + /** The parent of this DTD. */ + XMLParent getParent() { xmlDTDs(this,_,_,_,result) } + + /** A printable representation of this DTD. */ + string toString() { + (this.isPublic() and result = this.getRoot() + " PUBLIC '" + + this.getPublicId() + "' '" + + this.getSystemId() + "'") or + (not this.isPublic() and result = this.getRoot() + + " SYSTEM '" + + this.getSystemId() + "'") + } +} + +/** An XML tag in an XML file. */ +class XMLElement extends @xmlelement, XMLParent, XMLLocatable { + /** Whether this XML element has the given `name`. */ + predicate hasName(string name) { name = getName() } + + /** The name of this XML element. */ + override + string getName() { xmlElements(this,result,_,_,_) } + + /** The XML file in which this XML element occurs. */ + override + XMLFile getFile() { xmlElements(this,_,_,_,result) } + + /** The parent of this XML element. */ + XMLParent getParent() { xmlElements(this,_,result,_,_) } + + /** The index of this XML element among its parent's children. */ + int getIndex() { xmlElements(this, _, _, result, _) } + + /** Whether this XML element has a namespace. */ + predicate hasNamespace() { xmlHasNs(this,_,_) } + + /** The namespace of this XML element, if any. */ + XMLNamespace getNamespace() { xmlHasNs(this,result,_) } + + /** The index of this XML element among its parent's children. */ + int getElementPositionIndex() { xmlElements(this,_,_,result,_) } + + /** The depth of this element within the XML file tree structure. */ + override + int getDepth() { result = this.getParent().getDepth() + 1 } + + /** An XML attribute of this XML element. */ + XMLAttribute getAnAttribute() { result.getElement() = this } + + /** The attribute with the specified `name`, if any. */ + XMLAttribute getAttribute(string name) { + result.getElement() = this and result.getName() = name + } + + /** Whether this XML element has an attribute with the specified `name`. */ + predicate hasAttribute(string name) { + exists(XMLAttribute a| a = this.getAttribute(name)) + } + + /** The value of the attribute with the specified `name`, if any. */ + string getAttributeValue(string name) { + result = this.getAttribute(name).getValue() + } + + /** A printable representation of this XML element. */ + override + string toString() { result = XMLParent.super.toString() } +} + +/** An attribute that occurs inside an XML element. */ +class XMLAttribute extends @xmlattribute, XMLLocatable { + /** The name of this attribute. */ + string getName() { xmlAttrs(this,_,result,_,_,_) } + + /** The XML element to which this attribute belongs. */ + XMLElement getElement() { xmlAttrs(this,result,_,_,_,_) } + + /** Whether this attribute has a namespace. */ + predicate hasNamespace() { xmlHasNs(this,_,_) } + + /** The namespace of this attribute, if any. */ + XMLNamespace getNamespace() { xmlHasNs(this,result,_) } + + /** The value of this attribute. */ + string getValue() { xmlAttrs(this,_,_,result,_,_) } + + /** A printable representation of this XML attribute. */ + override string toString() { result = this.getName() + "=" + this.getValue() } +} + +/** A namespace used in an XML file */ +class XMLNamespace extends @xmlnamespace { + /** The prefix of this namespace. */ + string getPrefix() { xmlNs(this,result,_,_) } + + /** The URI of this namespace. */ + string getURI() { xmlNs(this,_,result,_) } + + /** Whether this namespace has no prefix. */ + predicate isDefault() { this.getPrefix() = "" } + + /** A printable representation of this XML namespace. */ + string toString() { + (this.isDefault() and result = this.getURI()) or + (not this.isDefault() and result = this.getPrefix() + ":" + this.getURI()) + } +} + +/** A comment of the form `` is an XML comment. */ +class XMLComment extends @xmlcomment, XMLLocatable { + /** The text content of this XML comment. */ + string getText() { xmlComments(this,result,_,_) } + + /** The parent of this XML comment. */ + XMLParent getParent() { xmlComments(this,_,result,_) } + + /** A printable representation of this XML comment. */ + override string toString() { result = this.getText() } +} + +/** + * A sequence of characters that occurs between opening and + * closing tags of an XML element, excluding other elements. + */ +class XMLCharacters extends @xmlcharacters, XMLLocatable { + /** The content of this character sequence. */ + string getCharacters() { xmlChars(this,result,_,_,_,_) } + + /** The parent of this character sequence. */ + XMLParent getParent() { xmlChars(this,_,result,_,_,_) } + + /** Whether this character sequence is CDATA. */ + predicate isCDATA() { xmlChars(this,_,_,_,1,_) } + + /** A printable representation of this XML character sequence. */ + override string toString() { result = this.getCharacters() } +} diff --git a/python/ql/src/semmlecode.python.dbscheme b/python/ql/src/semmlecode.python.dbscheme new file mode 100644 index 000000000000..62a30d37a729 --- /dev/null +++ b/python/ql/src/semmlecode.python.dbscheme @@ -0,0 +1,982 @@ +/* + * This dbscheme is auto-generated by 'semmle/dbscheme_gen.py'. + * WARNING: Any modifications to this file will be lost. + * Relations can be changed by modifying master.py or + * by adding rules to dbscheme.template + */ + + /* + * External artifacts + */ + +externalDefects( + unique int id : @externalDefect, + varchar(900) queryPath : string ref, + int location : @location ref, + varchar(900) message : string ref, + float severity : float ref +); + +externalMetrics( + unique int id : @externalMetric, + varchar(900) queryPath : string ref, + int location : @location ref, + float value : float ref +); + +externalData( + int id : @externalDataElement, + varchar(900) queryPath : string ref, + int column: int ref, + varchar(900) data : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + + +/* + * Duplicate code + */ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/* + * Line metrics + */ +py_codelines(int id : @py_scope ref, + int count : int ref); + +py_commentlines(int id : @py_scope ref, + int count : int ref); + +py_docstringlines(int id : @py_scope ref, + int count : int ref); + +py_alllines(int id : @py_scope ref, + int count : int ref); + +/* + * Version history + */ + +svnentries( + int id : @svnentry, + varchar(500) revision : string ref, + varchar(500) author : string ref, + date revisionDate : date ref, + int changeSize : int ref +) + +svnaffectedfiles( + int id : @svnentry ref, + int file : @file ref, + varchar(500) action : string ref +) + +svnentrymsg( + int id : @svnentry ref, + varchar(500) message : string ref +) + +svnchurn( + int commit : @svnentry ref, + int file : @file ref, + int addedLines : int ref, + int deletedLines : int ref +) + +/**************************** + Python dbscheme +****************************/ + +/* fromSource is ignored */ +files(unique int id: @file, + varchar(900) name: string ref, + varchar(900) simple: string ref, + varchar(900) ext: string ref, + int fromSource: int ref); + +folders(unique int id: @folder, + varchar(900) name: string ref, + varchar(900) simple: string ref); + +@container = @folder | @file; + +containerparent(int parent: @container ref, + unique int child: @container ref); + +@sourceline = @file | @py_Module | @xmllocatable; + +numlines(int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref + ); + +@location = @location_ast | @location_default ; + +locations_default(unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +locations_ast(unique int id: @location_ast, + int module: @py_Module ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +file_contents(unique int file: @file ref, string contents: string ref); + +py_module_path(int module: @py_Module ref, int file: @container ref); + +variable(unique int id : @py_variable, + int scope : @py_scope ref, + varchar(1) name : string ref); + +py_line_lengths(unique int id : @py_line, + int file: @py_Module ref, + int line : int ref, + int length : int ref); + +py_extracted_version(int module : @py_Module ref, + varchar(1) version : string ref); + +/* AUTO GENERATED PART STARTS HERE */ + + +/* AnnAssign.location = 0, location */ +/* AnnAssign.value = 1, expr */ +/* AnnAssign.annotation = 2, expr */ +/* AnnAssign.target = 3, expr */ + +/* Assert.location = 0, location */ +/* Assert.test = 1, expr */ +/* Assert.msg = 2, expr */ + +/* Assign.location = 0, location */ +/* Assign.value = 1, expr */ +/* Assign.targets = 2, expr_list */ + +/* AssignExpr.location = 0, location */ +/* AssignExpr.parenthesised = 1, bool */ +/* AssignExpr.value = 2, expr */ +/* AssignExpr.target = 3, expr */ + +/* Attribute.location = 0, location */ +/* Attribute.parenthesised = 1, bool */ +/* Attribute.value = 2, expr */ +/* Attribute.attr = 3, str */ +/* Attribute.ctx = 4, expr_context */ + +/* AugAssign.location = 0, location */ +/* AugAssign.operation = 1, BinOp */ + +/* Await.location = 0, location */ +/* Await.parenthesised = 1, bool */ +/* Await.value = 2, expr */ + +/* BinaryExpr.location = 0, location */ +/* BinaryExpr.parenthesised = 1, bool */ +/* BinaryExpr.left = 2, expr */ +/* BinaryExpr.op = 3, operator */ +/* BinaryExpr.right = 4, expr */ +/* BinaryExpr = AugAssign */ + +/* BoolExpr.location = 0, location */ +/* BoolExpr.parenthesised = 1, bool */ +/* BoolExpr.op = 2, boolop */ +/* BoolExpr.values = 3, expr_list */ + +/* Break.location = 0, location */ + +/* Bytes.location = 0, location */ +/* Bytes.parenthesised = 1, bool */ +/* Bytes.s = 2, bytes */ +/* Bytes.prefix = 3, bytes */ +/* Bytes.implicitly_concatenated_parts = 4, StringPart_list */ + +/* Call.location = 0, location */ +/* Call.parenthesised = 1, bool */ +/* Call.func = 2, expr */ +/* Call.positional_args = 3, expr_list */ +/* Call.named_args = 4, dict_item_list */ + +/* Class.name = 0, str */ +/* Class.body = 1, stmt_list */ +/* Class = ClassExpr */ + +/* ClassExpr.location = 0, location */ +/* ClassExpr.parenthesised = 1, bool */ +/* ClassExpr.name = 2, str */ +/* ClassExpr.bases = 3, expr_list */ +/* ClassExpr.keywords = 4, dict_item_list */ +/* ClassExpr.inner_scope = 5, Class */ + +/* Compare.location = 0, location */ +/* Compare.parenthesised = 1, bool */ +/* Compare.left = 2, expr */ +/* Compare.ops = 3, cmpop_list */ +/* Compare.comparators = 4, expr_list */ + +/* Continue.location = 0, location */ + +/* Delete.location = 0, location */ +/* Delete.targets = 1, expr_list */ + +/* Dict.location = 0, location */ +/* Dict.parenthesised = 1, bool */ +/* Dict.items = 2, dict_item_list */ + +/* DictComp.location = 0, location */ +/* DictComp.parenthesised = 1, bool */ +/* DictComp.function = 2, Function */ +/* DictComp.iterable = 3, expr */ + +/* DictUnpacking.location = 0, location */ +/* DictUnpacking.value = 1, expr */ + +/* Ellipsis.location = 0, location */ +/* Ellipsis.parenthesised = 1, bool */ + +/* ExceptStmt.location = 0, location */ +/* ExceptStmt.type = 1, expr */ +/* ExceptStmt.name = 2, expr */ +/* ExceptStmt.body = 3, stmt_list */ + +/* Exec.location = 0, location */ +/* Exec.body = 1, expr */ +/* Exec.globals = 2, expr */ +/* Exec.locals = 3, expr */ + +/* ExprStmt.location = 0, location */ +/* ExprStmt.value = 1, expr */ + +/* Filter.location = 0, location */ +/* Filter.parenthesised = 1, bool */ +/* Filter.value = 2, expr */ +/* Filter.filter = 3, expr */ + +/* For.location = 0, location */ +/* For.target = 1, expr */ +/* For.iter = 2, expr */ +/* For.body = 3, stmt_list */ +/* For.orelse = 4, stmt_list */ +/* For.is_async = 5, bool */ + +/* FormattedValue.location = 0, location */ +/* FormattedValue.parenthesised = 1, bool */ +/* FormattedValue.value = 2, expr */ +/* FormattedValue.conversion = 3, str */ +/* FormattedValue.format_spec = 4, JoinedStr */ + +/* Function.name = 0, str */ +/* Function.args = 1, parameter_list */ +/* Function.vararg = 2, expr */ +/* Function.kwonlyargs = 3, expr_list */ +/* Function.kwarg = 4, expr */ +/* Function.body = 5, stmt_list */ +/* Function.is_async = 6, bool */ +/* Function = FunctionParent */ + +/* FunctionExpr.location = 0, location */ +/* FunctionExpr.parenthesised = 1, bool */ +/* FunctionExpr.name = 2, str */ +/* FunctionExpr.args = 3, arguments */ +/* FunctionExpr.returns = 4, expr */ +/* FunctionExpr.inner_scope = 5, Function */ + +/* GeneratorExp.location = 0, location */ +/* GeneratorExp.parenthesised = 1, bool */ +/* GeneratorExp.function = 2, Function */ +/* GeneratorExp.iterable = 3, expr */ + +/* Global.location = 0, location */ +/* Global.names = 1, str_list */ + +/* If.location = 0, location */ +/* If.test = 1, expr */ +/* If.body = 2, stmt_list */ +/* If.orelse = 3, stmt_list */ + +/* IfExp.location = 0, location */ +/* IfExp.parenthesised = 1, bool */ +/* IfExp.test = 2, expr */ +/* IfExp.body = 3, expr */ +/* IfExp.orelse = 4, expr */ + +/* Import.location = 0, location */ +/* Import.names = 1, alias_list */ + +/* ImportExpr.location = 0, location */ +/* ImportExpr.parenthesised = 1, bool */ +/* ImportExpr.level = 2, int */ +/* ImportExpr.name = 3, str */ +/* ImportExpr.top = 4, bool */ + +/* ImportStar.location = 0, location */ +/* ImportStar.module = 1, expr */ + +/* ImportMember.location = 0, location */ +/* ImportMember.parenthesised = 1, bool */ +/* ImportMember.module = 2, expr */ +/* ImportMember.name = 3, str */ + +/* Fstring.location = 0, location */ +/* Fstring.parenthesised = 1, bool */ +/* Fstring.values = 2, expr_list */ +/* Fstring = FormattedValue */ + +/* KeyValuePair.location = 0, location */ +/* KeyValuePair.value = 1, expr */ +/* KeyValuePair.key = 2, expr */ + +/* Lambda.location = 0, location */ +/* Lambda.parenthesised = 1, bool */ +/* Lambda.args = 2, arguments */ +/* Lambda.inner_scope = 3, Function */ + +/* List.location = 0, location */ +/* List.parenthesised = 1, bool */ +/* List.elts = 2, expr_list */ +/* List.ctx = 3, expr_context */ + +/* ListComp.location = 0, location */ +/* ListComp.parenthesised = 1, bool */ +/* ListComp.function = 2, Function */ +/* ListComp.iterable = 3, expr */ +/* ListComp.generators = 4, comprehension_list */ +/* ListComp.elt = 5, expr */ + +/* Module.name = 0, str */ +/* Module.hash = 1, str */ +/* Module.body = 2, stmt_list */ +/* Module.kind = 3, str */ + +/* Name.location = 0, location */ +/* Name.parenthesised = 1, bool */ +/* Name.variable = 2, variable */ +/* Name.ctx = 3, expr_context */ +/* Name = ParameterList */ + +/* Nonlocal.location = 0, location */ +/* Nonlocal.names = 1, str_list */ + +/* Num.location = 0, location */ +/* Num.parenthesised = 1, bool */ +/* Num.n = 2, number */ +/* Num.text = 3, number */ + +/* Pass.location = 0, location */ + +/* PlaceHolder.location = 0, location */ +/* PlaceHolder.parenthesised = 1, bool */ +/* PlaceHolder.variable = 2, variable */ +/* PlaceHolder.ctx = 3, expr_context */ + +/* Print.location = 0, location */ +/* Print.dest = 1, expr */ +/* Print.values = 2, expr_list */ +/* Print.nl = 3, bool */ + +/* Raise.location = 0, location */ +/* Raise.exc = 1, expr */ +/* Raise.cause = 2, expr */ +/* Raise.type = 3, expr */ +/* Raise.inst = 4, expr */ +/* Raise.tback = 5, expr */ + +/* Repr.location = 0, location */ +/* Repr.parenthesised = 1, bool */ +/* Repr.value = 2, expr */ + +/* Return.location = 0, location */ +/* Return.value = 1, expr */ + +/* Set.location = 0, location */ +/* Set.parenthesised = 1, bool */ +/* Set.elts = 2, expr_list */ + +/* SetComp.location = 0, location */ +/* SetComp.parenthesised = 1, bool */ +/* SetComp.function = 2, Function */ +/* SetComp.iterable = 3, expr */ + +/* Slice.location = 0, location */ +/* Slice.parenthesised = 1, bool */ +/* Slice.start = 2, expr */ +/* Slice.stop = 3, expr */ +/* Slice.step = 4, expr */ + +/* Starred.location = 0, location */ +/* Starred.parenthesised = 1, bool */ +/* Starred.value = 2, expr */ +/* Starred.ctx = 3, expr_context */ + +/* Str.location = 0, location */ +/* Str.parenthesised = 1, bool */ +/* Str.s = 2, str */ +/* Str.prefix = 3, str */ +/* Str.implicitly_concatenated_parts = 4, StringPart_list */ + +/* StringPart.text = 0, str */ +/* StringPart.location = 1, location */ +/* StringPart = StringPartList */ +/* StringPartList = BytesOrStr */ + +/* Subscript.location = 0, location */ +/* Subscript.parenthesised = 1, bool */ +/* Subscript.value = 2, expr */ +/* Subscript.index = 3, expr */ +/* Subscript.ctx = 4, expr_context */ + +/* TemplateDottedNotation.location = 0, location */ +/* TemplateDottedNotation.parenthesised = 1, bool */ +/* TemplateDottedNotation.value = 2, expr */ +/* TemplateDottedNotation.attr = 3, str */ +/* TemplateDottedNotation.ctx = 4, expr_context */ + +/* TemplateWrite.location = 0, location */ +/* TemplateWrite.value = 1, expr */ + +/* Try.location = 0, location */ +/* Try.body = 1, stmt_list */ +/* Try.orelse = 2, stmt_list */ +/* Try.handlers = 3, stmt_list */ +/* Try.finalbody = 4, stmt_list */ + +/* Tuple.location = 0, location */ +/* Tuple.parenthesised = 1, bool */ +/* Tuple.elts = 2, expr_list */ +/* Tuple.ctx = 3, expr_context */ +/* Tuple = ParameterList */ + +/* UnaryExpr.location = 0, location */ +/* UnaryExpr.parenthesised = 1, bool */ +/* UnaryExpr.op = 2, unaryop */ +/* UnaryExpr.operand = 3, expr */ + +/* While.location = 0, location */ +/* While.test = 1, expr */ +/* While.body = 2, stmt_list */ +/* While.orelse = 3, stmt_list */ + +/* With.location = 0, location */ +/* With.context_expr = 1, expr */ +/* With.optional_vars = 2, expr */ +/* With.body = 3, stmt_list */ +/* With.is_async = 4, bool */ + +/* Yield.location = 0, location */ +/* Yield.parenthesised = 1, bool */ +/* Yield.value = 2, expr */ + +/* YieldFrom.location = 0, location */ +/* YieldFrom.parenthesised = 1, bool */ +/* YieldFrom.value = 2, expr */ + +/* Alias.value = 0, expr */ +/* Alias.asname = 1, expr */ +/* Alias = AliasList */ +/* AliasList = Import */ + +/* Arguments.kw_defaults = 0, expr_list */ +/* Arguments.defaults = 1, expr_list */ +/* Arguments.annotations = 2, expr_list */ +/* Arguments.varargannotation = 3, expr */ +/* Arguments.kwargannotation = 4, expr */ +/* Arguments.kw_annotations = 5, expr_list */ +/* Arguments = ArgumentsParent */ +/* boolean = BoolParent */ +/* Boolop = BoolExpr */ +/* string = Bytes */ +/* Cmpop = CmpopList */ +/* CmpopList = Compare */ + +/* Comprehension.location = 0, location */ +/* Comprehension.iter = 1, expr */ +/* Comprehension.target = 2, expr */ +/* Comprehension.ifs = 3, expr_list */ +/* Comprehension = ComprehensionList */ +/* ComprehensionList = ListComp */ +/* DictItem = DictItemList */ +/* DictItemList = DictItemListParent */ + +/* Expr.location = 0, location */ +/* Expr.parenthesised = 1, bool */ +/* Expr = ExprParent */ +/* ExprContext = ExprContextParent */ +/* ExprList = ExprListParent */ +/* int = ImportExpr */ + +/* Keyword.location = 0, location */ +/* Keyword.value = 1, expr */ +/* Keyword.arg = 2, str */ +/* Location = LocationParent */ +/* string = Num */ +/* Operator = BinaryExpr */ +/* ParameterList = Function */ + +/* Stmt.location = 0, location */ +/* Stmt = StmtList */ +/* StmtList = StmtListParent */ +/* string = StrParent */ +/* StringList = StrListParent */ +/* Unaryop = UnaryExpr */ +/* Variable = VariableParent */ +py_Classes(unique int id : @py_Class, + unique int parent : @py_ClassExpr ref); + +py_Functions(unique int id : @py_Function, + unique int parent : @py_Function_parent ref); + +py_Modules(unique int id : @py_Module); + +py_StringParts(unique int id : @py_StringPart, + int parent : @py_StringPart_list ref, + int idx : int ref); + +py_StringPart_lists(unique int id : @py_StringPart_list, + unique int parent : @py_Bytes_or_Str ref); + +py_aliases(unique int id : @py_alias, + int parent : @py_alias_list ref, + int idx : int ref); + +py_alias_lists(unique int id : @py_alias_list, + unique int parent : @py_Import ref); + +py_arguments(unique int id : @py_arguments, + unique int parent : @py_arguments_parent ref); + +py_bools(int parent : @py_bool_parent ref, + int idx : int ref); + +py_boolops(unique int id : @py_boolop, + int kind: int ref, + unique int parent : @py_BoolExpr ref); + +py_bytes(varchar(1) id : string ref, + int parent : @py_Bytes ref, + int idx : int ref); + +py_cmpops(unique int id : @py_cmpop, + int kind: int ref, + int parent : @py_cmpop_list ref, + int idx : int ref); + +py_cmpop_lists(unique int id : @py_cmpop_list, + unique int parent : @py_Compare ref); + +py_comprehensions(unique int id : @py_comprehension, + int parent : @py_comprehension_list ref, + int idx : int ref); + +py_comprehension_lists(unique int id : @py_comprehension_list, + unique int parent : @py_ListComp ref); + +py_dict_items(unique int id : @py_dict_item, + int kind: int ref, + int parent : @py_dict_item_list ref, + int idx : int ref); + +py_dict_item_lists(unique int id : @py_dict_item_list, + unique int parent : @py_dict_item_list_parent ref); + +py_exprs(unique int id : @py_expr, + int kind: int ref, + int parent : @py_expr_parent ref, + int idx : int ref); + +py_expr_contexts(unique int id : @py_expr_context, + int kind: int ref, + unique int parent : @py_expr_context_parent ref); + +py_expr_lists(unique int id : @py_expr_list, + int parent : @py_expr_list_parent ref, + int idx : int ref); + +py_ints(int id : int ref, + unique int parent : @py_ImportExpr ref); + +py_locations(unique int id : @location ref, + unique int parent : @py_location_parent ref); + +py_numbers(varchar(1) id : string ref, + int parent : @py_Num ref, + int idx : int ref); + +py_operators(unique int id : @py_operator, + int kind: int ref, + unique int parent : @py_BinaryExpr ref); + +py_parameter_lists(unique int id : @py_parameter_list, + unique int parent : @py_Function ref); + +py_stmts(unique int id : @py_stmt, + int kind: int ref, + int parent : @py_stmt_list ref, + int idx : int ref); + +py_stmt_lists(unique int id : @py_stmt_list, + int parent : @py_stmt_list_parent ref, + int idx : int ref); + +py_strs(varchar(1) id : string ref, + int parent : @py_str_parent ref, + int idx : int ref); + +py_str_lists(unique int id : @py_str_list, + unique int parent : @py_str_list_parent ref); + +py_unaryops(unique int id : @py_unaryop, + int kind: int ref, + unique int parent : @py_UnaryExpr ref); + +py_variables(int id : @py_variable ref, + unique int parent : @py_variable_parent ref); + +case @py_boolop.kind of + 0 = @py_And +| 1 = @py_Or; + +case @py_cmpop.kind of + 0 = @py_Eq +| 1 = @py_Gt +| 2 = @py_GtE +| 3 = @py_In +| 4 = @py_Is +| 5 = @py_IsNot +| 6 = @py_Lt +| 7 = @py_LtE +| 8 = @py_NotEq +| 9 = @py_NotIn; + +case @py_dict_item.kind of + 0 = @py_DictUnpacking +| 1 = @py_KeyValuePair +| 2 = @py_keyword; + +case @py_expr.kind of + 0 = @py_Attribute +| 1 = @py_BinaryExpr +| 2 = @py_BoolExpr +| 3 = @py_Bytes +| 4 = @py_Call +| 5 = @py_ClassExpr +| 6 = @py_Compare +| 7 = @py_Dict +| 8 = @py_DictComp +| 9 = @py_Ellipsis +| 10 = @py_FunctionExpr +| 11 = @py_GeneratorExp +| 12 = @py_IfExp +| 13 = @py_ImportExpr +| 14 = @py_ImportMember +| 15 = @py_Lambda +| 16 = @py_List +| 17 = @py_ListComp +| 18 = @py_Name +| 19 = @py_Num +| 20 = @py_Repr +| 21 = @py_Set +| 22 = @py_SetComp +| 23 = @py_Slice +| 24 = @py_Starred +| 25 = @py_Str +| 26 = @py_Subscript +| 27 = @py_Tuple +| 28 = @py_UnaryExpr +| 29 = @py_Yield +| 30 = @py_YieldFrom +| 31 = @py_TemplateDottedNotation +| 32 = @py_Filter +| 33 = @py_PlaceHolder +| 34 = @py_Await +| 35 = @py_Fstring +| 36 = @py_FormattedValue +| 37 = @py_AssignExpr; + +case @py_expr_context.kind of + 0 = @py_AugLoad +| 1 = @py_AugStore +| 2 = @py_Del +| 3 = @py_Load +| 4 = @py_Param +| 5 = @py_Store; + +case @py_operator.kind of + 0 = @py_Add +| 1 = @py_BitAnd +| 2 = @py_BitOr +| 3 = @py_BitXor +| 4 = @py_Div +| 5 = @py_FloorDiv +| 6 = @py_LShift +| 7 = @py_Mod +| 8 = @py_Mult +| 9 = @py_Pow +| 10 = @py_RShift +| 11 = @py_Sub +| 12 = @py_MatMult; + +case @py_stmt.kind of + 0 = @py_Assert +| 1 = @py_Assign +| 2 = @py_AugAssign +| 3 = @py_Break +| 4 = @py_Continue +| 5 = @py_Delete +| 6 = @py_ExceptStmt +| 7 = @py_Exec +| 8 = @py_Expr_stmt +| 9 = @py_For +| 10 = @py_Global +| 11 = @py_If +| 12 = @py_Import +| 13 = @py_ImportStar +| 14 = @py_Nonlocal +| 15 = @py_Pass +| 16 = @py_Print +| 17 = @py_Raise +| 18 = @py_Return +| 19 = @py_Try +| 20 = @py_While +| 21 = @py_With +| 22 = @py_TemplateWrite +| 23 = @py_AnnAssign; + +case @py_unaryop.kind of + 0 = @py_Invert +| 1 = @py_Not +| 2 = @py_UAdd +| 3 = @py_USub; + +@py_Bytes_or_Str = @py_Bytes | @py_Str; + +@py_Function_parent = @py_DictComp | @py_FunctionExpr | @py_GeneratorExp | @py_Lambda | @py_ListComp | @py_SetComp; + +@py_arguments_parent = @py_FunctionExpr | @py_Lambda; + +@py_ast_node = @py_Class | @py_Function | @py_Module | @py_StringPart | @py_comprehension | @py_dict_item | @py_expr | @py_stmt; + +@py_bool_parent = @py_For | @py_Function | @py_Print | @py_With | @py_expr; + +@py_dict_item_list_parent = @py_Call | @py_ClassExpr | @py_Dict; + +@py_expr_context_parent = @py_Attribute | @py_List | @py_Name | @py_PlaceHolder | @py_Starred | @py_Subscript | @py_TemplateDottedNotation | @py_Tuple; + +@py_expr_list_parent = @py_Assign | @py_BoolExpr | @py_Call | @py_ClassExpr | @py_Compare | @py_Delete | @py_Fstring | @py_Function | @py_List | @py_Print | @py_Set | @py_Tuple | @py_arguments | @py_comprehension; + +@py_expr_or_stmt = @py_expr | @py_stmt; + +@py_expr_parent = @py_AnnAssign | @py_Assert | @py_Assign | @py_AssignExpr | @py_Attribute | @py_AugAssign | @py_Await | @py_BinaryExpr | @py_Call | @py_Compare | @py_DictComp | @py_DictUnpacking | @py_ExceptStmt | @py_Exec | @py_Expr_stmt | @py_Filter | @py_For | @py_FormattedValue | @py_Function | @py_FunctionExpr | @py_GeneratorExp | @py_If | @py_IfExp | @py_ImportMember | @py_ImportStar | @py_KeyValuePair | @py_ListComp | @py_Print | @py_Raise | @py_Repr | @py_Return | @py_SetComp | @py_Slice | @py_Starred | @py_Subscript | @py_TemplateDottedNotation | @py_TemplateWrite | @py_UnaryExpr | @py_While | @py_With | @py_Yield | @py_YieldFrom | @py_alias | @py_arguments | @py_comprehension | @py_expr_list | @py_keyword | @py_parameter_list; + +@py_location_parent = @py_DictUnpacking | @py_KeyValuePair | @py_StringPart | @py_comprehension | @py_expr | @py_keyword | @py_stmt; + +@py_parameter = @py_Name | @py_Tuple; + +@py_scope = @py_Class | @py_Function | @py_Module; + +@py_stmt_list_parent = @py_Class | @py_ExceptStmt | @py_For | @py_Function | @py_If | @py_Module | @py_Try | @py_While | @py_With; + +@py_str_list_parent = @py_Global | @py_Nonlocal; + +@py_str_parent = @py_Attribute | @py_Class | @py_ClassExpr | @py_FormattedValue | @py_Function | @py_FunctionExpr | @py_ImportExpr | @py_ImportMember | @py_Module | @py_Str | @py_StringPart | @py_TemplateDottedNotation | @py_keyword | @py_str_list; + +@py_variable_parent = @py_Name | @py_PlaceHolder; + + +/* + * End of auto-generated part + */ + + + +/* Map relative names to absolute names for imports */ +py_absolute_names(int module : @py_Module ref, + varchar(1) relname : string ref, + varchar(1) absname : string ref); + +py_exports(int id : @py_Module ref, + varchar(1) name : string ref); + +/* Successor information */ +py_successors(int predecessor : @py_flow_node ref, + int successor : @py_flow_node ref); + +py_true_successors(int predecessor : @py_flow_node ref, + int successor : @py_flow_node ref); + +py_exception_successors(int predecessor : @py_flow_node ref, + int successor : @py_flow_node ref); + +py_false_successors(int predecessor : @py_flow_node ref, + int successor : @py_flow_node ref); + +py_flow_bb_node(unique int flownode : @py_flow_node, + int realnode : @py_ast_node ref, + int basicblock : @py_flow_node ref, + int index : int ref); + +py_scope_flow(int flow : @py_flow_node ref, + int scope : @py_scope ref, + int kind : int ref); + +py_idoms(unique int node : @py_flow_node ref, + int immediate_dominator : @py_flow_node ref); + +py_ssa_phi(int phi : @py_ssa_var ref, + int arg: @py_ssa_var ref); + +py_ssa_var(unique int id : @py_ssa_var, + int var : @py_variable ref); + +py_ssa_use(int node: @py_flow_node ref, + int var : @py_ssa_var ref); + +py_ssa_defn(unique int id : @py_ssa_var ref, + int node: @py_flow_node ref); + +@py_base_var = @py_variable | @py_ssa_var; + +py_scopes(unique int node : @py_expr_or_stmt ref, + int scope : @py_scope ref); + +py_scope_location(unique int id : @location ref, + unique int scope : @py_scope ref); + +py_flags_versioned(varchar(1) name : string ref, + varchar(1) value : string ref, + varchar(1) version : string ref); + +py_syntax_error_versioned(unique int id : @location ref, + varchar(1) message : string ref, + varchar(1) version : string ref); + +py_comments(unique int id : @py_comment, + varchar(1) text : string ref, + unique int location : @location ref); + +/* Type information support */ + +py_cobjects(unique int obj : @py_cobject); + +py_cobjecttypes(unique int obj : @py_cobject ref, + int typeof : @py_cobject ref); + +py_cobjectnames(unique int obj : @py_cobject ref, + varchar(1) name : string ref); + +/* Kind should be 0 for introspection, > 0 from source, as follows: + 1 from C extension source + */ +py_cobject_sources(int obj : @py_cobject ref, + int kind : int ref); + +py_cmembers_versioned(int object : @py_cobject ref, + varchar(1) name : string ref, + int member : @py_cobject ref, + varchar(1) version : string ref); + +py_citems(int object : @py_cobject ref, + int index : int ref, + int member : @py_cobject ref); + +ext_argtype(int funcid : @py_object ref, + int arg : int ref, + int typeid : @py_object ref); + +ext_rettype(int funcid : @py_object ref, + int typeid : @py_object ref); + +ext_proptype(int propid : @py_object ref, + int typeid : @py_object ref); + +ext_argreturn(int funcid : @py_object ref, + int arg : int ref); + +py_special_objects(unique int obj : @py_cobject ref, + unique varchar(1) name : string ref); + +py_decorated_object(int object : @py_object ref, + int level: int ref); + +@py_object = @py_cobject | @py_flow_node; + +@py_source_element = @py_ast_node | @container; + +/* XML Files */ + +xmlEncoding (unique int id: @file ref, varchar(900) encoding: string ref); + +xmlDTDs (unique int id: @xmldtd, + varchar(900) root: string ref, + varchar(900) publicId: string ref, + varchar(900) systemId: string ref, + int fileid: @file ref); + +xmlElements (unique int id: @xmlelement, + varchar(900) name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref); + +xmlAttrs (unique int id: @xmlattribute, + int elementid: @xmlelement ref, + varchar(900) name: string ref, + varchar(3600) value: string ref, + int idx: int ref, + int fileid: @file ref); + +xmlNs (int id: @xmlnamespace, + varchar(900) prefixName: string ref, + varchar(900) URI: string ref, + int fileid: @file ref); + +xmlHasNs (int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref); + +xmlComments (unique int id: @xmlcomment, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref); + +xmlChars (unique int id: @xmlcharacters, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations(int xmlElement: @xmllocatable ref, + int location: @location_default ref); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; diff --git a/python/ql/src/semmlecode.python.dbscheme.stats b/python/ql/src/semmlecode.python.dbscheme.stats new file mode 100644 index 000000000000..a8a501f76606 --- /dev/null +++ b/python/ql/src/semmlecode.python.dbscheme.stats @@ -0,0 +1,17472 @@ + + +@externalDefect +100 + + +@externalMetric +100 + + +@externalDataElement +20 + + +@duplication +890 + + +@similarity +5591 + + +@svnentry +100 + + +@file +3066 + + +@folder +686 + + +@location_default +100 + + +@location_ast +2310679 + + +@py_variable +242770 + + +@py_line +100 + + +@py_Class +10244 + + +@py_Function +44860 + + +@py_Module +5983 + + +@py_StringPart +6399 + + +@py_StringPart_list +2296 + + +@py_alias +21374 + + +@py_alias_list +14396 + + +@py_arguments +41982 + + +@py_boolop +10907 + + +@py_And +7243 + + +@py_Or +3663 + + +@py_cmpop +38007 + + +@py_Eq +11370 + + +@py_Gt +1999 + + +@py_GtE +1306 + + +@py_In +4743 + + +@py_Is +6368 + + +@py_IsNot +4541 + + +@py_Lt +1920 + + +@py_LtE +1128 + + +@py_NotEq +3050 + + +@py_NotIn +1672 + + +@py_cmpop_list +37666 + + +@py_comprehension +1688 + + +@py_comprehension_list +1682 + + +@py_dict_item +167901 + + +@py_DictUnpacking +1521 + + +@py_KeyValuePair +92837 + + +@py_keyword +74612 + + +@py_dict_item_list +33758 + + +@py_expr +1684031 + + +@py_Attribute +249565 + + +@py_BinaryExpr +28868 + + +@py_BoolExpr +10907 + + +@py_Bytes +105600 + + +@py_Call +198138 + + +@py_ClassExpr +10244 + + +@py_Compare +37666 + + +@py_Dict +9635 + + +@py_DictComp +99 + + +@py_Ellipsis +115 + + +@py_Fstring +100 + + +@py_FormattedValue +100 + + +@py_FunctionExpr +41531 + + +@py_GeneratorExp +1066 + + +@py_IfExp +923 + + +@py_ImportExpr +21532 + + +@py_ImportMember +17714 + + +@py_Lambda +870 + + +@py_List +23200 + + +@py_ListComp +1690 + + +@py_Name +845963 + + +@py_Num +58723 + + +@py_Set +261 + + +@py_SetComp +49 + + +@py_Slice +5316 + + +@py_Starred +1265 + + +@py_Str +288427 + + +@py_Subscript +31583 + + +@py_Tuple +27693 + + +@py_UnaryExpr +13295 + + +@py_Yield +3941 + + +@py_YieldFrom +398 + + +@py_Repr +100 + + +@py_TemplateDottedNotation +100 + + +@py_Filter +100 + + +@py_PlaceHolder +100 + + +@py_Await +500 + + +@py_AssignExpr +200 + + +@py_expr_context +1140675 + + +@py_Del +1324 + + +@py_Load +853094 + + +@py_Param +96047 + + +@py_Store +198700 + + +@py_AugLoad +100 + + +@py_AugStore +100 + + +@py_expr_list +430986 + + +@py_operator +28868 + + +@py_Add +13603 + + +@py_BitAnd +796 + + +@py_BitOr +799 + + +@py_BitXor +190 + + +@py_Div +393 + + +@py_FloorDiv +362 + + +@py_LShift +279 + + +@py_Mod +8234 + + +@py_Mult +2218 + + +@py_Pow +501 + + +@py_RShift +157 + + +@py_Sub +3136 + + +@py_MatMult +100 + + +@py_parameter_list +43271 + + +@py_stmt +372643 + + +@py_Assert +1999 + + +@py_Assign +151576 + + +@py_AugAssign +3656 + + +@py_Break +1699 + + +@py_Continue +1199 + + +@py_Delete +1149 + + +@py_ExceptStmt +5610 + + +@py_Expr_stmt +76750 + + +@py_For +11495 + + +@py_Global +392 + + +@py_If +53619 + + +@py_Import +14396 + + +@py_ImportStar +158 + + +@py_Nonlocal +35 + + +@py_Pass +2872 + + +@py_Raise +7794 + + +@py_Return +36127 + + +@py_Try +6210 + + +@py_While +2138 + + +@py_With +4193 + + +@py_Exec +43 + + +@py_Print +1032 + + +@py_TemplateWrite +100 + + +@py_AnnAssign +100 + + +@py_stmt_list +156700 + + +@py_str_list +427 + + +@py_unaryop +13295 + + +@py_Invert +107 + + +@py_Not +8655 + + +@py_UAdd +14 + + +@py_USub +4565 + + +@py_flow_node +2323431 + + +@py_ssa_var +272292 + + +@py_comment +77830 + + +@py_cobject +112856 + + +@xmldtd +100 + + +@xmlelement +100 + + +@xmlattribute +100 + + +@xmlnamespace +100 + + +@xmlcomment +100 + + +@xmlcharacters +100 + + + +externalDefects +100 + + +id +100 + + +queryPath +100 + + +location +100 + + +message +100 + + +severity +100 + + + + +id +queryPath + + +12 + + +1 +2 +2 + + + + + + +id +location + + +12 + + +1 +2 +2 + + + + + + +id +message + + +12 + + +1 +2 +2 + + + + + + +id +severity + + +12 + + +1 +2 +2 + + + + + + +queryPath +id + + +12 + + + + + +queryPath +location + + +12 + + + + + +queryPath +message + + +12 + + + + + +queryPath +severity + + +12 + + + + + +location +id + + +12 + + + + + +location +queryPath + + +12 + + + + + +location +message + + +12 + + + + + +location +severity + + +12 + + + + + +message +id + + +12 + + + + + +message +queryPath + + +12 + + + + + +message +location + + +12 + + + + + +message +severity + + +12 + + + + + +severity +id + + +12 + + + + + +severity +queryPath + + +12 + + + + + +severity +location + + +12 + + + + + +severity +message + + +12 + + + + + + + +externalMetrics +100 + + +id +100 + + +queryPath +100 + + +location +100 + + +value +100 + + + + +id +queryPath + + +12 + + +1 +2 +1 + + + + + + +id +location + + +12 + + +1 +2 +1 + + + + + + +id +value + + +12 + + +1 +2 +1 + + + + + + +queryPath +id + + +12 + + + + + +queryPath +location + + +12 + + + + + +queryPath +value + + +12 + + + + + +location +id + + +12 + + + + + +location +queryPath + + +12 + + + + + +location +value + + +12 + + + + + +value +id + + +12 + + + + + +value +queryPath + + +12 + + + + + +value +location + + +12 + + + + + + + +externalData +41 + + +id +20 + + +queryPath +2 + + +column +5 + + +data +41 + + + + +id +queryPath + + +12 + + +1 +2 +20 + + + + + + +id +column + + +12 + + +2 +3 +20 + + + + + + +id +data + + +12 + + +2 +3 +20 + + + + + + +queryPath +id + + +12 + + +7 +8 +2 + + + + + + +queryPath +column + + +12 + + +2 +3 +2 + + + + + + +queryPath +data + + +12 + + +14 +15 +2 + + + + + + +column +id + + +12 + + +7 +8 +5 + + + + + + +column +queryPath + + +12 + + +1 +2 +5 + + + + + + +column +data + + +12 + + +7 +8 +5 + + + + + + +data +id + + +12 + + +1 +2 +41 + + + + + + +data +queryPath + + +12 + + +1 +2 +41 + + + + + + +data +column + + +12 + + +1 +2 +41 + + + + + + + + +snapshotDate +2 + + +snapshotDate +2 + + + + + +sourceLocationPrefix +2 + + +prefix +2 + + + + + +duplicateCode +890 + + +id +890 + + +relativePath +91 + + +equivClass +415 + + + + +id +relativePath + + +12 + + +1 +2 +890 + + + + + + +id +equivClass + + +12 + + +1 +2 +890 + + + + + + +relativePath +id + + +12 + + +1 +2 +30 + + +2 +3 +16 + + +3 +4 +4 + + +4 +5 +8 + + +6 +8 +6 + + +8 +12 +6 + + +12 +19 +6 + + +23 +47 +6 + + +48 +109 +4 + + + + + + +relativePath +equivClass + + +12 + + +1 +2 +38 + + +2 +3 +12 + + +3 +4 +6 + + +4 +5 +8 + + +6 +10 +8 + + +10 +15 +6 + + +15 +46 +6 + + +92 +105 +2 + + + + + + +equivClass +id + + +12 + + +2 +3 +371 + + +3 +4 +31 + + +4 +7 +12 + + + + + + +equivClass +relativePath + + +12 + + +1 +2 +95 + + +2 +3 +288 + + +3 +5 +31 + + + + + + + + +similarCode +5591 + + +id +5591 + + +relativePath +347 + + +equivClass +1696 + + + + +id +relativePath + + +12 + + +1 +2 +5591 + + + + + + +id +equivClass + + +12 + + +1 +2 +5591 + + + + + + +relativePath +id + + +12 + + +1 +2 +44 + + +2 +3 +33 + + +3 +5 +31 + + +5 +7 +30 + + +7 +9 +18 + + +9 +11 +26 + + +11 +13 +26 + + +13 +18 +29 + + +18 +23 +29 + + +23 +30 +24 + + +30 +42 +26 + + +45 +155 +26 + + +161 +162 +1 + + + + + + +relativePath +equivClass + + +12 + + +1 +2 +66 + + +2 +3 +19 + + +3 +4 +20 + + +4 +5 +18 + + +5 +6 +18 + + +6 +8 +27 + + +8 +10 +30 + + +10 +13 +26 + + +13 +18 +26 + + +18 +23 +26 + + +23 +31 +31 + + +31 +53 +26 + + +54 +145 +9 + + + + + + +equivClass +id + + +12 + + +2 +3 +937 + + +3 +4 +260 + + +4 +5 +166 + + +5 +6 +88 + + +6 +8 +138 + + +8 +11 +105 + + + + + + +equivClass +relativePath + + +12 + + +1 +2 +358 + + +2 +3 +733 + + +3 +4 +216 + + +4 +5 +139 + + +5 +7 +110 + + +7 +10 +127 + + +10 +11 +9 + + + + + + + + +tokens +889686 + + +id +6481 + + +offset +10514 + + +beginLine +9882 + + +beginColumn +1197 + + +endLine +9882 + + +endColumn +1207 + + + + +id +offset + + +12 + + +100 +101 +394 + + +101 +102 +750 + + +102 +103 +347 + + +103 +104 +414 + + +104 +105 +405 + + +105 +107 +528 + + +107 +108 +414 + + +108 +111 +513 + + +111 +117 +555 + + +117 +127 +494 + + +127 +145 +490 + + +145 +176 +487 + + +176 +284 +488 + + +289 +7594 +196 + + + + + + +id +beginLine + + +12 + + +5 +9 +396 + + +9 +10 +299 + + +10 +11 +559 + + +11 +12 +432 + + +12 +13 +598 + + +13 +14 +747 + + +14 +15 +541 + + +15 +17 +564 + + +17 +20 +589 + + +20 +24 +573 + + +24 +28 +526 + + +28 +51 +498 + + +51 +1520 +155 + + + + + + +id +beginColumn + + +12 + + +9 +17 +516 + + +17 +22 +488 + + +22 +31 +563 + + +31 +37 +566 + + +37 +43 +585 + + +43 +46 +472 + + +46 +49 +591 + + +49 +51 +438 + + +51 +54 +571 + + +54 +56 +443 + + +56 +59 +484 + + +59 +68 +524 + + +68 +131 +234 + + + + + + +id +endLine + + +12 + + +5 +9 +396 + + +9 +10 +299 + + +10 +11 +559 + + +11 +12 +432 + + +12 +13 +598 + + +13 +14 +747 + + +14 +15 +541 + + +15 +17 +564 + + +17 +20 +589 + + +20 +24 +573 + + +24 +28 +526 + + +28 +51 +502 + + +51 +1520 +150 + + + + + + +id +endColumn + + +12 + + +10 +18 +450 + + +18 +23 +523 + + +23 +33 +531 + + +33 +39 +495 + + +39 +44 +504 + + +44 +48 +533 + + +48 +51 +544 + + +51 +54 +549 + + +54 +56 +492 + + +56 +58 +458 + + +58 +61 +508 + + +61 +67 +498 + + +67 +133 +391 + + + + + + +offset +id + + +12 + + +2 +3 +6935 + + +4 +5 +693 + + +6 +11 +706 + + +12 +15 +887 + + +16 +93 +790 + + +94 +4682 +499 + + + + + + +offset +beginLine + + +12 + + +2 +3 +6935 + + +4 +5 +693 + + +6 +11 +706 + + +12 +15 +891 + + +16 +91 +789 + + +91 +1817 +497 + + + + + + +offset +beginColumn + + +12 + + +1 +2 +6952 + + +2 +3 +722 + + +3 +5 +674 + + +5 +8 +969 + + +8 +41 +797 + + +41 +169 +397 + + + + + + +offset +endLine + + +12 + + +2 +3 +6935 + + +4 +5 +693 + + +6 +11 +706 + + +12 +15 +891 + + +16 +91 +789 + + +91 +1817 +497 + + + + + + +offset +endColumn + + +12 + + +1 +2 +6973 + + +2 +3 +696 + + +3 +6 +929 + + +6 +9 +801 + + +9 +57 +798 + + +57 +172 +314 + + + + + + +beginLine +id + + +12 + + +1 +2 +1613 + + +2 +3 +1931 + + +3 +4 +987 + + +4 +5 +650 + + +5 +7 +825 + + +7 +9 +744 + + +9 +12 +772 + + +12 +17 +836 + + +17 +37 +749 + + +37 +148 +742 + + +151 +217 +29 + + + + + + +beginLine +offset + + +12 + + +1 +4 +697 + + +4 +8 +882 + + +8 +11 +746 + + +11 +15 +883 + + +15 +20 +801 + + +20 +25 +756 + + +25 +32 +757 + + +32 +42 +743 + + +42 +55 +742 + + +55 +72 +778 + + +72 +98 +747 + + +98 +148 +751 + + +148 +211 +594 + + + + + + +beginLine +beginColumn + + +12 + + +1 +3 +749 + + +3 +6 +686 + + +6 +8 +605 + + +8 +10 +779 + + +10 +12 +733 + + +12 +14 +714 + + +14 +17 +726 + + +17 +21 +880 + + +21 +26 +872 + + +26 +32 +852 + + +32 +40 +810 + + +40 +54 +771 + + +54 +184 +699 + + + + + + +beginLine +endLine + + +12 + + +1 +2 +9740 + + +2 +4 +142 + + + + + + +beginLine +endColumn + + +12 + + +1 +3 +750 + + +3 +6 +666 + + +6 +8 +621 + + +8 +10 +722 + + +10 +12 +720 + + +12 +14 +699 + + +14 +17 +721 + + +17 +21 +890 + + +21 +26 +862 + + +26 +32 +839 + + +32 +40 +794 + + +40 +53 +790 + + +53 +81 +746 + + +81 +185 +56 + + + + + + +beginColumn +id + + +12 + + +1 +2 +389 + + +2 +3 +200 + + +3 +4 +80 + + +4 +7 +105 + + +7 +8 +90 + + +8 +11 +91 + + +11 +45 +91 + + +48 +2322 +90 + + +2328 +3928 +59 + + + + + + +beginColumn +offset + + +12 + + +1 +2 +404 + + +2 +3 +206 + + +3 +4 +65 + + +4 +7 +101 + + +7 +8 +88 + + +8 +11 +94 + + +11 +33 +90 + + +33 +345 +90 + + +360 +2645 +58 + + + + + + +beginColumn +beginLine + + +12 + + +1 +2 +628 + + +2 +3 +204 + + +3 +4 +90 + + +4 +10 +99 + + +10 +750 +90 + + +762 +5047 +84 + + + + + + +beginColumn +endLine + + +12 + + +1 +2 +628 + + +2 +3 +204 + + +3 +4 +90 + + +4 +10 +99 + + +10 +750 +90 + + +762 +5046 +84 + + + + + + +beginColumn +endColumn + + +12 + + +1 +2 +822 + + +2 +3 +152 + + +3 +6 +95 + + +6 +31 +92 + + +31 +99 +34 + + + + + + +endLine +id + + +12 + + +1 +2 +1613 + + +2 +3 +1931 + + +3 +4 +987 + + +4 +5 +652 + + +5 +7 +823 + + +7 +9 +744 + + +9 +12 +772 + + +12 +17 +836 + + +17 +37 +749 + + +37 +148 +742 + + +151 +217 +29 + + + + + + +endLine +offset + + +12 + + +1 +4 +702 + + +4 +8 +876 + + +8 +11 +749 + + +11 +15 +883 + + +15 +20 +801 + + +20 +25 +756 + + +25 +32 +753 + + +32 +42 +744 + + +42 +55 +743 + + +55 +72 +779 + + +72 +98 +746 + + +98 +148 +751 + + +148 +211 +594 + + + + + + +endLine +beginLine + + +12 + + +1 +2 +9734 + + +2 +3 +148 + + + + + + +endLine +beginColumn + + +12 + + +1 +3 +749 + + +3 +6 +685 + + +6 +8 +607 + + +8 +10 +782 + + +10 +12 +728 + + +12 +14 +714 + + +14 +17 +728 + + +17 +21 +880 + + +21 +26 +873 + + +26 +32 +851 + + +32 +40 +810 + + +40 +54 +771 + + +54 +184 +699 + + + + + + +endLine +endColumn + + +12 + + +1 +3 +750 + + +3 +6 +664 + + +6 +8 +625 + + +8 +10 +721 + + +10 +12 +718 + + +12 +14 +702 + + +14 +17 +721 + + +17 +21 +883 + + +21 +26 +862 + + +26 +32 +841 + + +32 +40 +797 + + +40 +53 +792 + + +53 +81 +743 + + +81 +185 +56 + + + + + + +endColumn +id + + +12 + + +1 +2 +391 + + +2 +3 +192 + + +3 +4 +84 + + +4 +7 +102 + + +7 +8 +92 + + +8 +11 +98 + + +11 +47 +91 + + +50 +2174 +91 + + +2189 +4114 +62 + + + + + + +endColumn +offset + + +12 + + +1 +2 +408 + + +2 +3 +193 + + +3 +4 +74 + + +4 +7 +95 + + +7 +8 +85 + + +8 +11 +103 + + +11 +36 +91 + + +37 +353 +91 + + +364 +1140 +62 + + + + + + +endColumn +beginLine + + +12 + + +1 +2 +625 + + +2 +3 +211 + + +3 +4 +84 + + +4 +8 +91 + + +8 +405 +91 + + +414 +3303 +91 + + +3320 +3523 +11 + + + + + + +endColumn +beginColumn + + +12 + + +1 +2 +812 + + +2 +3 +167 + + +3 +8 +95 + + +8 +33 +92 + + +33 +42 +38 + + + + + + +endColumn +endLine + + +12 + + +1 +2 +625 + + +2 +3 +211 + + +3 +4 +84 + + +4 +8 +91 + + +8 +405 +91 + + +414 +3303 +91 + + +3320 +3523 +11 + + + + + + + + +py_codelines +52985 + + +id +52985 + + +count +732 + + + + +id +count + + +12 + + +1 +2 +52985 + + + + + + +count +id + + +12 + + +1 +2 +307 + + +2 +3 +116 + + +3 +4 +59 + + +4 +6 +61 + + +6 +11 +62 + + +11 +28 +57 + + +28 +612 +55 + + +631 +13079 +15 + + + + + + + + +py_commentlines +52983 + + +id +52983 + + +count +198 + + + + +id +count + + +12 + + +1 +2 +52983 + + + + + + +count +id + + +12 + + +1 +2 +78 + + +2 +3 +26 + + +3 +4 +11 + + +4 +6 +16 + + +6 +10 +15 + + +10 +19 +15 + + +19 +48 +15 + + +49 +351 +15 + + +494 +40367 +7 + + + + + + + + +py_docstringlines +52983 + + +id +52983 + + +count +123 + + + + +id +count + + +12 + + +1 +2 +52983 + + + + + + +count +id + + +12 + + +1 +2 +20 + + +2 +3 +11 + + +3 +4 +9 + + +4 +5 +10 + + +5 +8 +11 + + +8 +13 +10 + + +14 +22 +11 + + +22 +29 +10 + + +29 +54 +10 + + +56 +175 +10 + + +232 +5368 +10 + + +36413 +36414 +1 + + + + + + + + +py_alllines +52983 + + +id +52983 + + +count +829 + + + + +id +count + + +12 + + +1 +2 +52983 + + + + + + +count +id + + +12 + + +1 +2 +361 + + +2 +3 +108 + + +3 +4 +68 + + +4 +5 +47 + + +5 +8 +69 + + +8 +17 +65 + + +17 +93 +64 + + +113 +9596 +47 + + + + + + + + +svnentries +100 + + +id +100 + + +revision +100 + + +author +100 + + +revisionDate +100 + + +changeSize +100 + + + + +id +revision + + +12 + + + + + +id +author + + +12 + + + + + +id +revisionDate + + +12 + + + + + +id +changeSize + + +12 + + + + + +revision +id + + +12 + + + + + +revision +author + + +12 + + + + + +revision +revisionDate + + +12 + + + + + +revision +changeSize + + +12 + + + + + +author +id + + +12 + + + + + +author +revision + + +12 + + + + + +author +revisionDate + + +12 + + + + + +author +changeSize + + +12 + + + + + +revisionDate +id + + +12 + + + + + +revisionDate +revision + + +12 + + + + + +revisionDate +author + + +12 + + + + + +revisionDate +changeSize + + +12 + + + + + +changeSize +id + + +12 + + + + + +changeSize +revision + + +12 + + + + + +changeSize +author + + +12 + + + + + +changeSize +revisionDate + + +12 + + + + + + + +svnaffectedfiles +100 + + +id +100 + + +file +100 + + +action +100 + + + + +id +file + + +12 + + + + + +id +action + + +12 + + + + + +file +id + + +12 + + + + + +file +action + + +12 + + + + + +action +id + + +12 + + + + + +action +file + + +12 + + + + + + + +svnentrymsg +100 + + +id +100 + + +message +100 + + + + +id +message + + +12 + + + + + +message +id + + +12 + + + + + + + +svnchurn +100 + + +commit +100 + + +file +100 + + +addedLines +100 + + +deletedLines +100 + + + + +commit +file + + +12 + + + + + +commit +addedLines + + +12 + + + + + +commit +deletedLines + + +12 + + + + + +file +commit + + +12 + + + + + +file +addedLines + + +12 + + + + + +file +deletedLines + + +12 + + + + + +addedLines +commit + + +12 + + + + + +addedLines +file + + +12 + + + + + +addedLines +deletedLines + + +12 + + + + + +deletedLines +commit + + +12 + + + + + +deletedLines +file + + +12 + + + + + +deletedLines +addedLines + + +12 + + + + + + + +files +3066 + + +id +3066 + + +name +3066 + + +simple +1294 + + +ext +1 + + +fromSource +1 + + + + +id +name + + +12 + + +1 +2 +3066 + + + + + + +id +simple + + +12 + + +1 +2 +3066 + + + + + + +id +ext + + +12 + + +1 +2 +3066 + + + + + + +id +fromSource + + +12 + + +1 +2 +3066 + + + + + + +name +id + + +12 + + +1 +2 +3066 + + + + + + +name +simple + + +12 + + +1 +2 +3066 + + + + + + +name +ext + + +12 + + +1 +2 +3066 + + + + + + +name +fromSource + + +12 + + +1 +2 +3066 + + + + + + +simple +id + + +12 + + +1 +2 +1058 + + +2 +3 +132 + + +3 +38 +98 + + +47 +646 +6 + + + + + + +simple +name + + +12 + + +1 +2 +1058 + + +2 +3 +132 + + +3 +38 +98 + + +47 +646 +6 + + + + + + +simple +ext + + +12 + + +1 +2 +1294 + + + + + + +simple +fromSource + + +12 + + +1 +2 +1294 + + + + + + +ext +id + + +12 + + +3066 +3067 +1 + + + + + + +ext +name + + +12 + + +3066 +3067 +1 + + + + + + +ext +simple + + +12 + + +1294 +1295 +1 + + + + + + +ext +fromSource + + +12 + + +1 +2 +1 + + + + + + +fromSource +id + + +12 + + +3066 +3067 +1 + + + + + + +fromSource +name + + +12 + + +3066 +3067 +1 + + + + + + +fromSource +simple + + +12 + + +1294 +1295 +1 + + + + + + +fromSource +ext + + +12 + + +1 +2 +1 + + + + + + + + +folders +686 + + +id +686 + + +name +686 + + +simple +538 + + + + +id +name + + +12 + + +1 +2 +686 + + + + + + +id +simple + + +12 + + +1 +2 +686 + + + + + + +name +id + + +12 + + +1 +2 +686 + + + + + + +name +simple + + +12 + + +1 +2 +686 + + + + + + +simple +id + + +12 + + +1 +2 +481 + + +2 +4 +45 + + +4 +27 +12 + + + + + + +simple +name + + +12 + + +1 +2 +481 + + +2 +4 +45 + + +4 +27 +12 + + + + + + + + +containerparent +3750 + + +parent +685 + + +child +3750 + + + + +parent +child + + +12 + + +1 +2 +53 + + +2 +3 +202 + + +3 +4 +176 + + +4 +5 +57 + + +5 +6 +34 + + +6 +8 +56 + + +8 +13 +54 + + +13 +149 +52 + + +204 +205 +1 + + + + + + +child +parent + + +12 + + +1 +2 +3750 + + + + + + + + +numlines +2553 + + +element_id +2553 + + +num_lines +687 + + +num_code +648 + + +num_comment +193 + + + + +element_id +num_lines + + +12 + + +1 +2 +2553 + + + + + + +element_id +num_code + + +12 + + +1 +2 +2553 + + + + + + +element_id +num_comment + + +12 + + +1 +2 +2553 + + + + + + +num_lines +element_id + + +12 + + +1 +2 +345 + + +2 +3 +129 + + +3 +4 +44 + + +4 +6 +57 + + +6 +11 +54 + + +11 +34 +52 + + +35 +60 +6 + + + + + + +num_lines +num_code + + +12 + + +1 +2 +348 + + +2 +3 +134 + + +3 +4 +46 + + +4 +5 +41 + + +5 +6 +39 + + +6 +9 +60 + + +9 +17 +19 + + + + + + +num_lines +num_comment + + +12 + + +1 +2 +348 + + +2 +3 +134 + + +3 +4 +46 + + +4 +5 +41 + + +5 +6 +39 + + +6 +9 +60 + + +9 +17 +19 + + + + + + +num_code +element_id + + +12 + + +1 +2 +319 + + +2 +3 +110 + + +3 +4 +53 + + +4 +6 +56 + + +6 +11 +54 + + +11 +36 +49 + + +36 +56 +7 + + + + + + +num_code +num_lines + + +12 + + +1 +2 +321 + + +2 +3 +110 + + +3 +4 +62 + + +4 +5 +38 + + +5 +7 +52 + + +7 +10 +51 + + +10 +14 +14 + + + + + + +num_code +num_comment + + +12 + + +1 +2 +321 + + +2 +3 +110 + + +3 +4 +62 + + +4 +5 +38 + + +5 +7 +52 + + +7 +10 +51 + + +10 +14 +14 + + + + + + +num_comment +element_id + + +12 + + +1 +2 +72 + + +2 +3 +29 + + +3 +4 +16 + + +4 +5 +15 + + +5 +8 +12 + + +8 +13 +15 + + +13 +29 +16 + + +30 +98 +15 + + +112 +578 +3 + + + + + + +num_comment +num_lines + + +12 + + +1 +2 +72 + + +2 +3 +29 + + +3 +4 +16 + + +4 +5 +15 + + +5 +8 +12 + + +8 +13 +15 + + +13 +26 +15 + + +27 +75 +16 + + +75 +112 +3 + + + + + + +num_comment +num_code + + +12 + + +1 +2 +72 + + +2 +3 +29 + + +3 +4 +16 + + +4 +5 +15 + + +5 +8 +12 + + +8 +13 +15 + + +13 +26 +15 + + +27 +75 +16 + + +75 +112 +3 + + + + + + + + +locations_default +100 + + +id +100 + + +file +100 + + +beginLine +100 + + +beginColumn +100 + + +endLine +100 + + +endColumn +100 + + + + +id +file + + +12 + + +1 +2 +2 + + + + + + +id +beginLine + + +12 + + +1 +2 +2 + + + + + + +id +beginColumn + + +12 + + +1 +2 +2 + + + + + + +id +endLine + + +12 + + +1 +2 +2 + + + + + + +id +endColumn + + +12 + + +1 +2 +2 + + + + + + +file +id + + +12 + + + + + +file +beginLine + + +12 + + + + + +file +beginColumn + + +12 + + + + + +file +endLine + + +12 + + + + + +file +endColumn + + +12 + + + + + +beginLine +id + + +12 + + + + + +beginLine +file + + +12 + + + + + +beginLine +beginColumn + + +12 + + + + + +beginLine +endLine + + +12 + + + + + +beginLine +endColumn + + +12 + + + + + +beginColumn +id + + +12 + + + + + +beginColumn +file + + +12 + + + + + +beginColumn +beginLine + + +12 + + + + + +beginColumn +endLine + + +12 + + + + + +beginColumn +endColumn + + +12 + + + + + +endLine +id + + +12 + + + + + +endLine +file + + +12 + + + + + +endLine +beginLine + + +12 + + + + + +endLine +beginColumn + + +12 + + + + + +endLine +endColumn + + +12 + + + + + +endColumn +id + + +12 + + + + + +endColumn +file + + +12 + + + + + +endColumn +beginLine + + +12 + + + + + +endColumn +beginColumn + + +12 + + + + + +endColumn +endLine + + +12 + + + + + + + +locations_ast +2310679 + + +id +2310679 + + +module +1527 + + +beginLine +12546 + + +beginColumn +2819 + + +endLine +12539 + + +endColumn +2939 + + + + +id +module + + +12 + + +1 +2 +2310679 + + + + + + +id +beginLine + + +12 + + +1 +2 +2310679 + + + + + + +id +beginColumn + + +12 + + +1 +2 +2310679 + + + + + + +id +endLine + + +12 + + +1 +2 +2310679 + + + + + + +id +endColumn + + +12 + + +1 +2 +2310679 + + + + + + +module +id + + +12 + + +1 +2 +288 + + +2 +30 +114 + + +30 +159 +114 + + +159 +276 +114 + + +279 +427 +116 + + +434 +716 +114 + + +719 +1003 +114 + + +1007 +1409 +116 + + +1426 +1860 +114 + + +1862 +2782 +114 + + +2798 +5578 +114 + + +5667 +58828 +87 + + + + + + +module +beginLine + + +12 + + +1 +2 +288 + + +2 +17 +116 + + +17 +42 +114 + + +42 +72 +116 + + +72 +113 +116 + + +114 +165 +116 + + +167 +231 +116 + + +232 +314 +114 + + +314 +411 +114 + + +413 +634 +114 + + +640 +1326 +114 + + +1326 +6932 +83 + + + + + + +module +beginColumn + + +12 + + +1 +2 +288 + + +2 +7 +114 + + +7 +29 +117 + + +29 +41 +119 + + +41 +49 +126 + + +49 +56 +137 + + +56 +60 +110 + + +60 +64 +123 + + +64 +68 +117 + + +68 +74 +127 + + +74 +91 +116 + + +91 +1405 +29 + + + + + + +module +endLine + + +12 + + +1 +2 +288 + + +2 +17 +117 + + +17 +43 +119 + + +44 +74 +121 + + +74 +117 +114 + + +117 +173 +114 + + +173 +238 +114 + + +238 +322 +114 + + +326 +421 +114 + + +421 +666 +116 + + +668 +1461 +114 + + +1472 +6948 +74 + + + + + + +module +endColumn + + +12 + + +1 +2 +288 + + +2 +18 +116 + + +18 +45 +114 + + +45 +59 +130 + + +59 +65 +131 + + +65 +69 +108 + + +69 +72 +109 + + +72 +75 +114 + + +75 +79 +121 + + +79 +86 +120 + + +86 +99 +120 + + +99 +1425 +51 + + + + + + +beginLine +id + + +12 + + +1 +8 +783 + + +8 +11 +960 + + +11 +15 +1027 + + +15 +20 +1012 + + +20 +27 +1050 + + +27 +36 +995 + + +36 +49 +1003 + + +49 +66 +977 + + +66 +107 +951 + + +107 +170 +949 + + +170 +297 +947 + + +297 +636 +941 + + +637 +2279 +941 + + +2283 +2351 +2 + + + + + + +beginLine +module + + +12 + + +1 +2 +1188 + + +2 +3 +1761 + + +3 +4 +510 + + +4 +5 +792 + + +5 +6 +792 + + +6 +9 +1114 + + +9 +11 +726 + + +11 +14 +1084 + + +14 +25 +955 + + +25 +42 +942 + + +42 +71 +976 + + +71 +177 +942 + + +177 +1104 +758 + + + + + + +beginLine +beginColumn + + +12 + + +1 +6 +995 + + +6 +8 +486 + + +8 +9 +780 + + +9 +11 +1091 + + +11 +13 +952 + + +13 +16 +1093 + + +16 +19 +954 + + +19 +23 +1128 + + +23 +29 +954 + + +29 +38 +972 + + +38 +47 +980 + + +47 +59 +976 + + +59 +75 +984 + + +75 +542 +196 + + + + + + +beginLine +endLine + + +12 + + +1 +2 +3511 + + +2 +3 +3490 + + +3 +4 +1501 + + +4 +5 +767 + + +5 +7 +1110 + + +7 +10 +988 + + +10 +17 +1010 + + +17 +51 +166 + + + + + + +beginLine +endColumn + + +12 + + +1 +5 +672 + + +5 +7 +785 + + +7 +9 +868 + + +9 +12 +1028 + + +12 +16 +1156 + + +16 +20 +952 + + +20 +25 +1052 + + +25 +30 +983 + + +30 +40 +1003 + + +40 +52 +959 + + +52 +64 +1026 + + +64 +74 +951 + + +74 +89 +965 + + +89 +546 +141 + + + + + + +beginColumn +id + + +12 + + +1 +2 +1542 + + +2 +3 +877 + + +3 +5 +213 + + +5 +250154 +185 + + + + + + +beginColumn +module + + +12 + + +1 +2 +2376 + + +2 +3 +238 + + +3 +1104 +204 + + + + + + +beginColumn +beginLine + + +12 + + +1 +2 +1542 + + +2 +3 +882 + + +3 +6 +220 + + +6 +7984 +174 + + + + + + +beginColumn +endLine + + +12 + + +1 +2 +1542 + + +2 +3 +882 + + +3 +6 +220 + + +6 +7972 +174 + + + + + + +beginColumn +endColumn + + +12 + + +1 +2 +2295 + + +2 +3 +304 + + +3 +114 +211 + + +120 +161 +6 + + + + + + +endLine +id + + +12 + + +1 +8 +793 + + +8 +11 +965 + + +11 +15 +996 + + +15 +20 +1005 + + +20 +27 +1056 + + +27 +36 +1016 + + +36 +49 +981 + + +49 +65 +966 + + +65 +106 +956 + + +106 +169 +951 + + +169 +295 +947 + + +295 +626 +941 + + +627 +2214 +941 + + +2217 +2349 +19 + + + + + + +endLine +module + + +12 + + +1 +2 +1210 + + +2 +3 +1754 + + +3 +4 +526 + + +4 +5 +797 + + +5 +6 +760 + + +6 +9 +1109 + + +9 +11 +732 + + +11 +14 +1078 + + +14 +25 +947 + + +25 +42 +956 + + +42 +70 +942 + + +70 +170 +941 + + +170 +1104 +782 + + + + + + +endLine +beginLine + + +12 + + +1 +2 +4048 + + +2 +3 +3046 + + +3 +4 +1345 + + +4 +5 +851 + + +5 +7 +1021 + + +7 +10 +1010 + + +10 +17 +1010 + + +17 +34 +203 + + + + + + +endLine +beginColumn + + +12 + + +1 +6 +999 + + +6 +9 +1140 + + +9 +11 +1056 + + +11 +13 +933 + + +13 +16 +1154 + + +16 +19 +992 + + +19 +23 +1129 + + +23 +29 +999 + + +29 +38 +981 + + +38 +47 +983 + + +47 +59 +985 + + +59 +75 +988 + + +75 +542 +192 + + + + + + +endLine +endColumn + + +12 + + +1 +6 +1045 + + +6 +8 +1010 + + +8 +11 +1073 + + +11 +14 +933 + + +14 +18 +1055 + + +18 +23 +1084 + + +23 +28 +1020 + + +28 +36 +984 + + +36 +48 +999 + + +48 +60 +991 + + +60 +70 +959 + + +70 +84 +963 + + +84 +547 +418 + + + + + + +endColumn +id + + +12 + + +1 +2 +1505 + + +2 +3 +972 + + +3 +5 +227 + + +5 +41083 +221 + + +42453 +55223 +13 + + + + + + +endColumn +module + + +12 + + +1 +2 +2435 + + +2 +3 +264 + + +3 +782 +221 + + +782 +1104 +18 + + + + + + +endColumn +beginLine + + +12 + + +1 +2 +1606 + + +2 +3 +902 + + +3 +6 +228 + + +6 +6777 +202 + + + + + + +endColumn +beginColumn + + +12 + + +1 +2 +2250 + + +2 +3 +408 + + +3 +56 +221 + + +56 +79 +59 + + + + + + +endColumn +endLine + + +12 + + +1 +2 +1606 + + +2 +3 +902 + + +3 +6 +228 + + +6 +6726 +202 + + + + + + + + +py_module_path +3066 + + +module +3066 + + +file +3066 + + + + +module +file + + +12 + + +1 +2 +3066 + + + + + + +file +module + + +12 + + +1 +2 +3066 + + + + + + + + +file_contents +100 + + +file +3066 + + +contents +100 + + + + +file +contents + + +12 + + +1 +2 +100 + + + + + + +contents +file + + +12 + + +1 +2 +100 + + + + + + + + +variable +242770 + + +id +242770 + + +scope +50174 + + +name +54891 + + + + +id +scope + + +12 + + +1 +2 +242770 + + + + + + +id +name + + +12 + + +1 +2 +242770 + + + + + + +scope +id + + +12 + + +1 +2 +10764 + + +2 +3 +14394 + + +3 +4 +7657 + + +4 +5 +4580 + + +5 +6 +2991 + + +6 +9 +4606 + + +9 +22 +3819 + + +22 +233 +1360 + + + + + + +scope +name + + +12 + + +1 +2 +10764 + + +2 +3 +14394 + + +3 +4 +7657 + + +4 +5 +4580 + + +5 +6 +2991 + + +6 +9 +4606 + + +9 +22 +3819 + + +22 +233 +1360 + + + + + + +name +id + + +12 + + +1 +2 +36525 + + +2 +3 +8506 + + +3 +5 +4396 + + +5 +20 +4134 + + +20 +10542 +1327 + + + + + + +name +scope + + +12 + + +1 +2 +36525 + + +2 +3 +8506 + + +3 +5 +4396 + + +5 +20 +4134 + + +20 +10542 +1327 + + + + + + + + +py_line_lengths +100 + + +id +100 + + +file +100 + + +line +100 + + +length +100 + + + + +id +file + + +12 + + +1 +2 +2 + + + + + + +id +line + + +12 + + +1 +2 +2 + + + + + + +id +length + + +12 + + +1 +2 +2 + + + + + + +file +id + + +12 + + + + + +file +line + + +12 + + + + + +file +length + + +12 + + + + + +line +id + + +12 + + + + + +line +file + + +12 + + + + + +line +length + + +12 + + + + + +length +id + + +12 + + + + + +length +file + + +12 + + + + + +length +line + + +12 + + + + + + + +py_Classes +10244 + + +id +10244 + + +parent +10244 + + + + +id +parent + + +12 + + +1 +2 +10244 + + + + + + +parent +id + + +12 + + +1 +2 +10244 + + + + + + + + +py_Functions +44860 + + +id +44860 + + +parent +44860 + + + + +id +parent + + +12 + + +1 +2 +44860 + + + + + + +parent +id + + +12 + + +1 +2 +44860 + + + + + + + + +py_Modules +5983 + + +id +5983 + + + + + +py_extracted_version +3337 + + +module +3337 + + +version +1 + + + + +module +version + + +12 + + +1 +2 +3337 + + + + + + +version +module + + +12 + + +3337 +3338 +1 + + + + + + + + +py_StringParts +6399 + + +id +6399 + + +parent +2296 + + +idx +62 + + + + +id +parent + + +12 + + +1 +2 +6399 + + + + + + +id +idx + + +12 + + +1 +2 +6399 + + + + + + +parent +id + + +12 + + +2 +3 +1598 + + +3 +4 +380 + + +4 +5 +142 + + +5 +63 +176 + + + + + + +parent +idx + + +12 + + +2 +3 +1598 + + +3 +4 +380 + + +4 +5 +142 + + +5 +63 +176 + + + + + + +idx +id + + +12 + + +4 +5 +17 + + +5 +6 +23 + + +6 +9 +5 + + +9 +14 +5 + + +16 +59 +5 + + +72 +699 +5 + + +2296 +2297 +2 + + + + + + +idx +parent + + +12 + + +4 +5 +17 + + +5 +6 +23 + + +6 +9 +5 + + +9 +14 +5 + + +16 +59 +5 + + +72 +699 +5 + + +2296 +2297 +2 + + + + + + + + +py_StringPart_lists +2296 + + +id +2296 + + +parent +2296 + + + + +id +parent + + +12 + + +1 +2 +2296 + + + + + + +parent +id + + +12 + + +1 +2 +2296 + + + + + + + + +py_aliases +21374 + + +id +21374 + + +parent +14396 + + +idx +110 + + + + +id +parent + + +12 + + +1 +2 +21374 + + + + + + +id +idx + + +12 + + +1 +2 +21374 + + + + + + +parent +id + + +12 + + +1 +2 +11488 + + +2 +3 +1597 + + +3 +7 +1116 + + +7 +111 +195 + + + + + + +parent +idx + + +12 + + +1 +2 +11488 + + +2 +3 +1597 + + +3 +7 +1116 + + +7 +111 +195 + + + + + + +idx +id + + +12 + + +1 +2 +21 + + +2 +3 +2 + + +3 +4 +30 + + +4 +5 +4 + + +5 +6 +9 + + +6 +9 +10 + + +9 +15 +8 + + +18 +32 +9 + + +36 +113 +9 + + +142 +14397 +8 + + + + + + +idx +parent + + +12 + + +1 +2 +21 + + +2 +3 +2 + + +3 +4 +30 + + +4 +5 +4 + + +5 +6 +9 + + +6 +9 +10 + + +9 +15 +8 + + +18 +32 +9 + + +36 +113 +9 + + +142 +14397 +8 + + + + + + + + +py_alias_lists +14396 + + +id +14396 + + +parent +14396 + + + + +id +parent + + +12 + + +1 +2 +14396 + + + + + + +parent +id + + +12 + + +1 +2 +14396 + + + + + + + + +py_arguments +41982 + + +id +41982 + + +parent +41982 + + + + +id +parent + + +12 + + +1 +2 +41982 + + + + + + +parent +id + + +12 + + +1 +2 +41982 + + + + + + + + +py_bools +26986 + + +parent +26986 + + +idx +3 + + + + +parent +idx + + +12 + + +1 +2 +26986 + + + + + + +idx +parent + + +12 + + +964 +965 +1 + + +3487 +3488 +1 + + +22535 +22536 +1 + + + + + + + + +py_boolops +10907 + + +id +10907 + + +kind +2 + + +parent +10907 + + + + +id +kind + + +12 + + +1 +2 +10907 + + + + + + +id +parent + + +12 + + +1 +2 +10907 + + + + + + +kind +id + + +12 + + +2646 +2647 +1 + + +5231 +5232 +1 + + + + + + +kind +parent + + +12 + + +2646 +2647 +1 + + +5231 +5232 +1 + + + + + + +parent +id + + +12 + + +1 +2 +10907 + + + + + + +parent +kind + + +12 + + +1 +2 +10907 + + + + + + + + +py_bytes +211200 + + +id +48658 + + +parent +105600 + + +idx +2 + + + + +id +parent + + +12 + + +1 +2 +37453 + + +2 +3 +6003 + + +3 +8 +3791 + + +8 +71667 +1411 + + + + + + +id +idx + + +12 + + +1 +2 +48644 + + +2 +3 +14 + + + + + + +parent +id + + +12 + + +1 +2 +14 + + +2 +3 +105586 + + + + + + +parent +idx + + +12 + + +2 +3 +105600 + + + + + + +idx +id + + +12 + + +14 +15 +1 + + +48658 +48659 +1 + + + + + + +idx +parent + + +12 + + +105600 +105601 +2 + + + + + + + + +py_cmpops +38007 + + +id +38007 + + +kind +29 + + +parent +37666 + + +idx +8 + + + + +id +kind + + +12 + + +1 +2 +38007 + + + + + + +id +parent + + +12 + + +1 +2 +38007 + + + + + + +id +idx + + +12 + + +1 +2 +38007 + + + + + + +kind +id + + +12 + + +380 +381 +2 + + +440 +441 +2 + + +563 +564 +2 + + +615 +616 +2 + + +673 +674 +2 + + +1027 +1028 +2 + + +1529 +1530 +2 + + +1597 +1598 +2 + + +2144 +2145 +2 + + +3828 +3829 +2 + + + + + + +kind +parent + + +12 + + +317 +318 +2 + + +439 +440 +2 + + +563 +564 +2 + + +612 +613 +2 + + +669 +670 +2 + + +1027 +1028 +2 + + +1529 +1530 +2 + + +1597 +1598 +2 + + +2144 +2145 +2 + + +3819 +3820 +2 + + + + + + +kind +idx + + +12 + + +1 +2 +11 + + +2 +3 +14 + + +3 +4 +2 + + + + + + +parent +id + + +12 + + +1 +2 +37330 + + +2 +4 +335 + + + + + + +parent +kind + + +12 + + +1 +2 +37562 + + +2 +3 +103 + + + + + + +parent +idx + + +12 + + +1 +2 +37330 + + +2 +4 +335 + + + + + + +idx +id + + +12 + + +2 +3 +2 + + +113 +114 +2 + + +12681 +12682 +2 + + + + + + +idx +kind + + +12 + + +1 +2 +2 + + +6 +7 +2 + + +10 +11 +2 + + + + + + +idx +parent + + +12 + + +2 +3 +2 + + +113 +114 +2 + + +12681 +12682 +2 + + + + + + + + +py_cmpop_lists +37666 + + +id +37666 + + +parent +37666 + + + + +id +parent + + +12 + + +1 +2 +37666 + + + + + + +parent +id + + +12 + + +1 +2 +37666 + + + + + + + + +py_comprehensions +1688 + + +id +1688 + + +parent +1682 + + +idx +2 + + + + +id +parent + + +12 + + +1 +2 +1688 + + + + + + +id +idx + + +12 + + +1 +2 +1688 + + + + + + +parent +id + + +12 + + +1 +2 +1676 + + +2 +3 +6 + + + + + + +parent +idx + + +12 + + +1 +2 +1676 + + +2 +3 +6 + + + + + + +idx +id + + +12 + + +6 +7 +1 + + +1682 +1683 +1 + + + + + + +idx +parent + + +12 + + +6 +7 +1 + + +1682 +1683 +1 + + + + + + + + +py_comprehension_lists +1682 + + +id +1682 + + +parent +1682 + + + + +id +parent + + +12 + + +1 +2 +1682 + + + + + + +parent +id + + +12 + + +1 +2 +1682 + + + + + + + + +py_dict_items +167901 + + +id +167901 + + +kind +4 + + +parent +19804 + + +idx +7730 + + + + +id +kind + + +12 + + +1 +2 +167901 + + + + + + +id +parent + + +12 + + +1 +2 +167901 + + + + + + +id +idx + + +12 + + +1 +2 +167901 + + + + + + +kind +id + + +12 + + +326 +327 +1 + + +53883 +53884 +1 + + +67045 +67046 +1 + + + + + + +kind +parent + + +12 + + +326 +327 +1 + + +1881 +1882 +1 + + +12123 +12124 +1 + + + + + + +kind +idx + + +12 + + +7 +8 +1 + + +18 +19 +1 + + +5583 +5584 +1 + + + + + + +parent +id + + +12 + + +1 +2 +5811 + + +2 +3 +1851 + + +3 +6 +1700 + + +6 +7 +8083 + + +7 +12 +1826 + + +12 +5584 +530 + + + + + + +parent +kind + + +12 + + +1 +2 +19765 + + +2 +3 +38 + + + + + + +parent +idx + + +12 + + +1 +2 +5811 + + +2 +3 +1851 + + +3 +6 +1700 + + +6 +7 +8083 + + +7 +12 +1826 + + +12 +5584 +530 + + + + + + +idx +id + + +12 + + +1 +2 +1654 + + +2 +3 +1982 + + +3 +4 +811 + + +4 +6 +192 + + +6 +7 +753 + + +7 +8 +962 + + +8 +20 +610 + + +20 +69 +584 + + +69 +14303 +178 + + + + + + +idx +kind + + +12 + + +1 +2 +7705 + + +2 +4 +24 + + + + + + +idx +parent + + +12 + + +1 +2 +1654 + + +2 +3 +1982 + + +3 +4 +811 + + +4 +6 +192 + + +6 +7 +753 + + +7 +8 +962 + + +8 +20 +610 + + +20 +69 +584 + + +69 +14303 +178 + + + + + + + + +py_dict_item_lists +33758 + + +id +33758 + + +parent +33758 + + + + +id +parent + + +12 + + +1 +2 +33758 + + + + + + +parent +id + + +12 + + +1 +2 +33758 + + + + + + + + +py_exprs +1684031 + + +id +1684031 + + +kind +89 + + +parent +1380134 + + +idx +597 + + + + +id +kind + + +12 + + +1 +2 +1684031 + + + + + + +id +parent + + +12 + + +1 +2 +1684031 + + + + + + +id +idx + + +12 + + +1 +2 +1684031 + + + + + + +kind +id + + +12 + + +15 +28 +5 + + +39 +89 +5 + + +134 +189 +5 + + +281 +360 +5 + + +426 +570 +5 + + +1056 +1205 +5 + + +1327 +1791 +5 + + +1942 +3179 +5 + + +3398 +4019 +5 + + +4476 +4980 +5 + + +8519 +9720 +5 + + +10633 +12682 +5 + + +13945 +16376 +5 + + +46173 +58988 +5 + + +75624 +284809 +5 + + + + + + +kind +parent + + +12 + + +15 +28 +5 + + +39 +87 +5 + + +134 +175 +5 + + +271 +359 +5 + + +426 +560 +5 + + +1036 +1119 +5 + + +1327 +1791 +5 + + +1942 +3179 +5 + + +3357 +3716 +5 + + +4285 +4980 +5 + + +8177 +9473 +5 + + +10060 +11624 +5 + + +13945 +15094 +5 + + +35526 +57772 +5 + + +72662 +245283 +5 + + + + + + +kind +idx + + +12 + + +1 +2 +8 + + +2 +3 +17 + + +3 +4 +2 + + +5 +6 +5 + + +6 +7 +11 + + +8 +9 +2 + + +9 +10 +5 + + +11 +12 +5 + + +12 +13 +5 + + +15 +18 +5 + + +23 +27 +5 + + +37 +127 +5 + + +201 +202 +2 + + + + + + +parent +id + + +12 + + +1 +2 +1147073 + + +2 +3 +197316 + + +3 +202 +35744 + + + + + + +parent +kind + + +12 + + +1 +2 +1255206 + + +2 +3 +120198 + + +3 +11 +4728 + + + + + + +parent +idx + + +12 + + +1 +2 +1147073 + + +2 +3 +197316 + + +3 +202 +35744 + + + + + + +idx +id + + +12 + + +1 +2 +23 + + +2 +3 +199 + + +3 +4 +148 + + +4 +6 +35 + + +6 +8 +50 + + +9 +26 +47 + + +26 +102 +47 + + +113 +197687 +44 + + + + + + +idx +kind + + +12 + + +1 +2 +222 + + +2 +3 +258 + + +3 +4 +8 + + +4 +5 +47 + + +5 +21 +47 + + +22 +29 +11 + + + + + + +idx +parent + + +12 + + +1 +2 +23 + + +2 +3 +199 + + +3 +4 +148 + + +4 +6 +35 + + +6 +8 +50 + + +9 +26 +47 + + +26 +102 +47 + + +113 +197687 +44 + + + + + + + + +py_expr_contexts +1140675 + + +id +1140675 + + +kind +11 + + +parent +1140675 + + + + +id +kind + + +12 + + +1 +2 +1140675 + + + + + + +id +parent + + +12 + + +1 +2 +1140675 + + + + + + +kind +id + + +12 + + +446 +447 +2 + + +29477 +29478 +2 + + +66896 +66897 +2 + + +287209 +287210 +2 + + + + + + +kind +parent + + +12 + + +446 +447 +2 + + +29477 +29478 +2 + + +66896 +66897 +2 + + +287209 +287210 +2 + + + + + + +parent +id + + +12 + + +1 +2 +1140675 + + + + + + +parent +kind + + +12 + + +1 +2 +1140675 + + + + + + + + +py_expr_lists +430986 + + +id +430986 + + +parent +423623 + + +idx +17 + + + + +id +parent + + +12 + + +1 +2 +430986 + + + + + + +id +idx + + +12 + + +1 +2 +430986 + + + + + + +parent +id + + +12 + + +1 +2 +416966 + + +2 +5 +6656 + + + + + + +parent +idx + + +12 + + +1 +2 +416966 + + +2 +5 +6656 + + + + + + +idx +id + + +12 + + +175 +176 +5 + + +2522 +2523 +2 + + +12681 +12682 +2 + + +54095 +54096 +2 + + +75451 +75452 +2 + + + + + + +idx +parent + + +12 + + +175 +176 +5 + + +2522 +2523 +2 + + +12681 +12682 +2 + + +54095 +54096 +2 + + +75451 +75452 +2 + + + + + + + + +py_ints +21532 + + +id +4 + + +parent +21532 + + + + +id +parent + + +12 + + +2 +3 +1 + + +207 +208 +1 + + +2770 +2771 +1 + + +18553 +18554 +1 + + + + + + +parent +id + + +12 + + +1 +2 +21532 + + + + + + + + +py_locations +2184728 + + +id +2184728 + + +parent +2184728 + + + + +id +parent + + +12 + + +1 +2 +2184728 + + + + + + +parent +id + + +12 + + +1 +2 +2184728 + + + + + + + + +py_numbers +117446 + + +id +4249 + + +parent +58723 + + +idx +2 + + + + +id +parent + + +12 + + +1 +2 +2830 + + +2 +3 +632 + + +3 +4 +291 + + +4 +11 +320 + + +11 +15704 +176 + + + + + + +id +idx + + +12 + + +1 +2 +1355 + + +2 +3 +2894 + + + + + + +parent +id + + +12 + + +1 +2 +57251 + + +2 +3 +1472 + + + + + + +parent +idx + + +12 + + +2 +3 +58723 + + + + + + +idx +id + + +12 + + +3302 +3303 +1 + + +3841 +3842 +1 + + + + + + +idx +parent + + +12 + + +58723 +58724 +2 + + + + + + + + +py_operators +28868 + + +id +28868 + + +kind +35 + + +parent +28868 + + + + +id +kind + + +12 + + +1 +2 +28868 + + + + + + +id +parent + + +12 + + +1 +2 +28868 + + + + + + +kind +id + + +12 + + +53 +54 +2 + + +64 +65 +2 + + +94 +95 +2 + + +121 +122 +2 + + +122 +123 +2 + + +169 +170 +2 + + +268 +269 +2 + + +269 +270 +2 + + +747 +748 +2 + + +1056 +1057 +2 + + +2176 +2177 +2 + + +4580 +4581 +2 + + + + + + +kind +parent + + +12 + + +53 +54 +2 + + +64 +65 +2 + + +94 +95 +2 + + +121 +122 +2 + + +122 +123 +2 + + +169 +170 +2 + + +268 +269 +2 + + +269 +270 +2 + + +747 +748 +2 + + +1056 +1057 +2 + + +2176 +2177 +2 + + +4580 +4581 +2 + + + + + + +parent +id + + +12 + + +1 +2 +28868 + + + + + + +parent +kind + + +12 + + +1 +2 +28868 + + + + + + + + +py_parameter_lists +43271 + + +id +43271 + + +parent +43271 + + + + +id +parent + + +12 + + +1 +2 +43271 + + + + + + +parent +id + + +12 + + +1 +2 +43271 + + + + + + + + +py_stmts +372643 + + +id +372643 + + +kind +59 + + +parent +156700 + + +idx +888 + + + + +id +kind + + +12 + + +1 +2 +372643 + + + + + + +id +parent + + +12 + + +1 +2 +372643 + + + + + + +id +idx + + +12 + + +1 +2 +372643 + + + + + + +kind +id + + +12 + + +12 +13 +2 + + +47 +48 +2 + + +132 +133 +2 + + +387 +388 +2 + + +404 +405 +2 + + +559 +560 +2 + + +572 +573 +2 + + +673 +674 +2 + + +720 +721 +2 + + +967 +968 +2 + + +1231 +1232 +2 + + +1889 +1890 +2 + + +2091 +2092 +2 + + +2624 +2625 +2 + + +3001 +3002 +2 + + +3870 +3871 +2 + + +12163 +12164 +2 + + +18052 +18053 +2 + + +25032 +25033 +2 + + +51031 +51032 +2 + + + + + + +kind +parent + + +12 + + +12 +13 +2 + + +37 +38 +2 + + +123 +124 +2 + + +356 +357 +2 + + +404 +405 +2 + + +471 +472 +2 + + +557 +558 +2 + + +572 +573 +2 + + +677 +678 +2 + + +967 +968 +2 + + +984 +985 +2 + + +1094 +1095 +2 + + +1777 +1778 +2 + + +1895 +1896 +2 + + +2624 +2625 +2 + + +3544 +3545 +2 + + +12163 +12164 +2 + + +12758 +12759 +2 + + +18445 +18446 +2 + + +20426 +20427 +2 + + + + + + +kind +idx + + +12 + + +2 +3 +5 + + +6 +7 +2 + + +7 +8 +5 + + +8 +9 +2 + + +13 +14 +2 + + +15 +16 +2 + + +18 +19 +5 + + +21 +22 +2 + + +27 +28 +2 + + +33 +34 +2 + + +37 +38 +2 + + +38 +39 +2 + + +42 +43 +2 + + +51 +52 +2 + + +84 +85 +2 + + +187 +188 +2 + + +293 +294 +2 + + + + + + +parent +id + + +12 + + +1 +2 +96284 + + +2 +3 +25704 + + +3 +4 +11789 + + +4 +7 +14376 + + +7 +300 +8545 + + + + + + +parent +kind + + +12 + + +1 +2 +106000 + + +2 +3 +31003 + + +3 +4 +12071 + + +4 +9 +7624 + + + + + + +parent +idx + + +12 + + +1 +2 +96284 + + +2 +3 +25704 + + +3 +4 +11789 + + +4 +7 +14376 + + +7 +300 +8545 + + + + + + +idx +id + + +12 + + +1 +2 +335 + + +2 +5 +59 + + +5 +6 +83 + + +6 +14 +74 + + +14 +25 +68 + + +25 +53 +68 + + +53 +103 +68 + + +107 +335 +68 + + +369 +52757 +62 + + + + + + +idx +kind + + +12 + + +1 +2 +344 + + +2 +3 +267 + + +3 +4 +83 + + +4 +5 +62 + + +5 +10 +71 + + +10 +21 +59 + + + + + + +idx +parent + + +12 + + +1 +2 +335 + + +2 +5 +59 + + +5 +6 +83 + + +6 +14 +74 + + +14 +25 +68 + + +25 +53 +68 + + +53 +103 +68 + + +107 +335 +68 + + +369 +52757 +62 + + + + + + + + +py_stmt_lists +156700 + + +id +156700 + + +parent +132647 + + +idx +14 + + + + +id +parent + + +12 + + +1 +2 +156700 + + + + + + +id +idx + + +12 + + +1 +2 +156700 + + + + + + +parent +id + + +12 + + +1 +2 +109538 + + +2 +3 +22179 + + +3 +5 +929 + + + + + + +parent +idx + + +12 + + +1 +2 +109538 + + +2 +3 +22179 + + +3 +5 +929 + + + + + + +idx +id + + +12 + + +460 +461 +2 + + +4033 +4034 +2 + + +13686 +13687 +2 + + +15103 +15104 +2 + + +19474 +19475 +2 + + + + + + +idx +parent + + +12 + + +460 +461 +2 + + +4033 +4034 +2 + + +13686 +13687 +2 + + +15103 +15104 +2 + + +19474 +19475 +2 + + + + + + + + +py_strs +985327 + + +id +140335 + + +parent +695288 + + +idx +5 + + + + +id +parent + + +12 + + +1 +2 +79968 + + +2 +3 +31802 + + +3 +4 +9602 + + +4 +8 +11026 + + +8 +143732 +7935 + + + + + + +id +idx + + +12 + + +1 +2 +106110 + + +2 +3 +22027 + + +3 +4 +12190 + + +4 +5 +6 + + + + + + +parent +id + + +12 + + +1 +2 +405951 + + +2 +3 +289317 + + +3 +5 +19 + + + + + + +parent +idx + + +12 + + +1 +2 +405275 + + +2 +3 +289993 + + +3 +5 +19 + + + + + + +idx +id + + +12 + + +34 +35 +1 + + +17059 +17060 +1 + + +25371 +25372 +1 + + +92414 +92415 +1 + + + + + + +idx +parent + + +12 + + +42 +43 +1 + + +37559 +37560 +1 + + +294366 +294367 +1 + + +379612 +379613 +1 + + + + + + + + +py_str_lists +427 + + +id +427 + + +parent +427 + + + + +id +parent + + +12 + + +1 +2 +427 + + + + + + +parent +id + + +12 + + +1 +2 +427 + + + + + + + + +py_unaryops +13295 + + +id +13295 + + +kind +11 + + +parent +13295 + + + + +id +kind + + +12 + + +1 +2 +13295 + + + + + + +id +parent + + +12 + + +1 +2 +13295 + + + + + + +kind +id + + +12 + + +5 +6 +2 + + +20 +21 +2 + + +1537 +1538 +2 + + +2914 +2915 +2 + + + + + + +kind +parent + + +12 + + +5 +6 +2 + + +20 +21 +2 + + +1537 +1538 +2 + + +2914 +2915 +2 + + + + + + +parent +id + + +12 + + +1 +2 +13295 + + + + + + +parent +kind + + +12 + + +1 +2 +13295 + + + + + + + + +py_variables +845963 + + +id +242770 + + +parent +845963 + + + + +id +parent + + +12 + + +1 +2 +61149 + + +2 +3 +77254 + + +3 +4 +38584 + + +4 +5 +21392 + + +5 +7 +20913 + + +7 +15 +18418 + + +15 +318 +5058 + + + + + + +parent +id + + +12 + + +1 +2 +845963 + + + + + + + + +py_absolute_names +100 + + +module +100 + + +relname +100 + + +absname +100 + + + + +module +relname + + +12 + + + + + +module +absname + + +12 + + + + + +relname +module + + +12 + + + + + +relname +absname + + +12 + + + + + +absname +module + + +12 + + + + + +absname +relname + + +12 + + + + + + + +py_exports +19755 + + +id +1138 + + +name +16813 + + + + +id +name + + +12 + + +1 +2 +141 + + +2 +3 +164 + + +3 +4 +109 + + +4 +5 +112 + + +5 +7 +103 + + +7 +10 +91 + + +10 +14 +88 + + +14 +20 +90 + + +20 +33 +94 + + +33 +53 +90 + + +53 +2260 +52 + + + + + + +name +id + + +12 + + +1 +2 +16070 + + +2 +143 +742 + + + + + + + + +py_successors +2366367 + + +predecessor +2270167 + + +successor +2275369 + + + + +predecessor +successor + + +12 + + +1 +2 +2177926 + + +2 +9 +92240 + + + + + + +successor +predecessor + + +12 + + +1 +2 +2225590 + + +2 +173 +49778 + + + + + + + + +py_true_successors +70315 + + +predecessor +70315 + + +successor +67897 + + + + +predecessor +successor + + +12 + + +1 +2 +70315 + + + + + + +successor +predecessor + + +12 + + +1 +2 +65747 + + +2 +7 +2150 + + + + + + + + +py_exception_successors +43951 + + +predecessor +39261 + + +successor +6911 + + + + +predecessor +successor + + +12 + + +1 +2 +35379 + + +2 +3 +3448 + + +3 +7 +433 + + + + + + +successor +predecessor + + +12 + + +1 +2 +1045 + + +2 +3 +1497 + + +3 +4 +1271 + + +4 +5 +760 + + +5 +6 +463 + + +6 +8 +519 + + +8 +12 +525 + + +12 +27 +534 + + +27 +173 +294 + + + + + + + + +py_false_successors +69439 + + +predecessor +69439 + + +successor +59260 + + + + +predecessor +successor + + +12 + + +1 +2 +69439 + + + + + + +successor +predecessor + + +12 + + +1 +2 +51296 + + +2 +3 +6510 + + +3 +13 +1452 + + + + + + + + +py_flow_bb_node +2323431 + + +flownode +2323431 + + +realnode +2208164 + + +basicblock +215280 + + +index +23948 + + + + +flownode +realnode + + +12 + + +1 +2 +2323431 + + + + + + +flownode +basicblock + + +12 + + +1 +2 +2323431 + + + + + + +flownode +index + + +12 + + +1 +2 +2323431 + + + + + + +realnode +flownode + + +12 + + +1 +2 +2102771 + + +2 +9 +105392 + + + + + + +realnode +basicblock + + +12 + + +1 +2 +2135213 + + +2 +7 +72950 + + + + + + +realnode +index + + +12 + + +1 +2 +2155174 + + +2 +5 +52989 + + + + + + +basicblock +flownode + + +12 + + +1 +2 +37515 + + +2 +3 +17987 + + +3 +4 +19072 + + +4 +5 +17365 + + +5 +6 +17931 + + +6 +7 +13664 + + +7 +8 +10900 + + +8 +10 +16975 + + +10 +13 +17232 + + +13 +19 +17763 + + +19 +26 +16605 + + +26 +17296 +12265 + + + + + + +basicblock +realnode + + +12 + + +1 +2 +37832 + + +2 +3 +17905 + + +3 +4 +19216 + + +4 +5 +18823 + + +5 +6 +16929 + + +6 +7 +13644 + + +7 +8 +11703 + + +8 +10 +16817 + + +10 +13 +16741 + + +13 +19 +17322 + + +19 +26 +16368 + + +26 +17295 +11973 + + + + + + +basicblock +index + + +12 + + +1 +2 +37515 + + +2 +3 +17987 + + +3 +4 +19072 + + +4 +5 +17365 + + +5 +6 +17931 + + +6 +7 +13664 + + +7 +8 +10900 + + +8 +10 +16975 + + +10 +13 +17232 + + +13 +19 +17763 + + +19 +26 +16605 + + +26 +17296 +12265 + + + + + + +index +flownode + + +12 + + +1 +2 +4957 + + +2 +3 +4220 + + +3 +4 +1805 + + +4 +6 +1253 + + +6 +8 +1750 + + +8 +9 +2240 + + +9 +10 +2678 + + +10 +19 +1819 + + +19 +60 +1815 + + +60 +155471 +1408 + + + + + + +index +realnode + + +12 + + +1 +2 +4957 + + +2 +3 +4220 + + +3 +4 +1805 + + +4 +6 +1253 + + +6 +8 +1750 + + +8 +9 +2240 + + +9 +10 +2678 + + +10 +19 +1819 + + +19 +60 +1815 + + +60 +141411 +1408 + + + + + + +index +basicblock + + +12 + + +1 +2 +4957 + + +2 +3 +4220 + + +3 +4 +1805 + + +4 +6 +1253 + + +6 +8 +1750 + + +8 +9 +2240 + + +9 +10 +2678 + + +10 +19 +1819 + + +19 +60 +1815 + + +60 +155471 +1408 + + + + + + + + +py_scope_flow +405895 + + +flow +405895 + + +scope +56616 + + +kind +4 + + + + +flow +scope + + +12 + + +1 +2 +405895 + + + + + + +flow +kind + + +12 + + +1 +2 +405895 + + + + + + +scope +flow + + +12 + + +2 +3 +15663 + + +3 +4 +8677 + + +4 +5 +7135 + + +5 +6 +4823 + + +6 +7 +3426 + + +7 +9 +4807 + + +9 +13 +5102 + + +13 +23 +4277 + + +23 +767 +2706 + + + + + + +scope +kind + + +12 + + +2 +3 +16115 + + +3 +4 +39685 + + +4 +5 +816 + + + + + + +kind +flow + + +12 + + +18869 +18870 +1 + + +37919 +37920 +1 + + +56616 +56617 +1 + + +292491 +292492 +1 + + + + + + +kind +scope + + +12 + + +18869 +18870 +1 + + +37919 +37920 +1 + + +41145 +41146 +1 + + +56616 +56617 +1 + + + + + + + + +py_idoms +2275369 + + +node +2275369 + + +immediate_dominator +2207166 + + + + +node +immediate_dominator + + +12 + + +1 +2 +2275369 + + + + + + +immediate_dominator +node + + +12 + + +1 +2 +2153132 + + +2 +11 +54033 + + + + + + + + +py_ssa_phi +46687 + + +phi +21496 + + +arg +44830 + + + + +phi +arg + + +12 + + +1 +2 +1782 + + +2 +3 +16149 + + +3 +4 +2560 + + +4 +23 +1003 + + + + + + +arg +phi + + +12 + + +1 +2 +43208 + + +2 +8 +1621 + + + + + + + + +py_ssa_var +272292 + + +id +272292 + + +var +217265 + + + + +id +var + + +12 + + +1 +2 +272292 + + + + + + +var +id + + +12 + + +1 +2 +194518 + + +2 +4 +16728 + + +4 +35 +6017 + + + + + + + + +py_ssa_use +487906 + + +node +421169 + + +var +239604 + + + + +node +var + + +12 + + +1 +2 +416004 + + +2 +185 +5165 + + + + + + +var +node + + +12 + + +1 +2 +151110 + + +2 +3 +42380 + + +3 +4 +18095 + + +4 +7 +18656 + + +7 +203 +9362 + + + + + + + + +py_ssa_defn +267795 + + +id +267795 + + +node +261828 + + + + +id +node + + +12 + + +1 +2 +267795 + + + + + + +node +id + + +12 + + +1 +2 +258774 + + +2 +81 +3053 + + + + + + + + +py_scopes +2056674 + + +node +2056674 + + +scope +51911 + + + + +node +scope + + +12 + + +1 +2 +2056674 + + + + + + +scope +node + + +12 + + +1 +5 +3923 + + +5 +7 +3611 + + +7 +9 +3715 + + +9 +11 +3941 + + +11 +14 +4776 + + +14 +17 +3965 + + +17 +22 +4491 + + +22 +28 +4078 + + +28 +37 +4161 + + +37 +50 +3938 + + +50 +72 +3914 + + +72 +118 +3953 + + +118 +5003 +3439 + + + + + + + + +py_scope_location +56618 + + +id +56618 + + +scope +56618 + + + + +id +scope + + +12 + + +1 +2 +56618 + + + + + + +scope +id + + +12 + + +1 +2 +56618 + + + + + + + + +py_flags_versioned +136 + + +name +136 + + +value +83 + + +version +2 + + + + +name +value + + +12 + + +1 +2 +136 + + + + + + +value +name + + +12 + + +1 +2 +68 + + +2 +3 +11 + + +15 +16 +2 + + + + + + + + +py_syntax_error_versioned +30 + + +id +30 + + +message +4 + + +version +2 + + + + +id +message + + +12 + + +1 +2 +30 + + + + + + +message +id + + +12 + + +1 +2 +1 + + +4 +5 +1 + + +17 +18 +1 + + + + + + + + +py_comments +77830 + + +id +77830 + + +text +61555 + + +location +77830 + + + + +id +text + + +12 + + +1 +2 +77830 + + + + + + +id +location + + +12 + + +1 +2 +77830 + + + + + + +text +id + + +12 + + +1 +2 +56275 + + +2 +5 +4845 + + +5 +942 +434 + + + + + + +text +location + + +12 + + +1 +2 +56275 + + +2 +5 +4845 + + +5 +942 +434 + + + + + + +location +id + + +12 + + +1 +2 +77830 + + + + + + +location +text + + +12 + + +1 +2 +77830 + + + + + + + + +py_cobjects +112856 + + +obj +112856 + + + + + +py_cobjecttypes +111600 + + +obj +111600 + + +typeof +65 + + + + +obj +typeof + + +12 + + +1 +2 +111600 + + + + + + +typeof +obj + + +12 + + +1 +2 +27 + + +2 +3 +4 + + +3 +5 +5 + + +6 +19 +5 + + +19 +54 +5 + + +58 +295 +5 + + +325 +857 +5 + + +923 +73625 +5 + + + + + + + + +py_cobjectnames +111600 + + +obj +111600 + + +name +106332 + + + + +obj +name + + +12 + + +1 +2 +111600 + + + + + + +name +obj + + +12 + + +1 +2 +105898 + + +2 +413 +434 + + + + + + + + +py_cobject_sources +114955 + + +obj +112856 + + +kind +2 + + + + +obj +kind + + +12 + + +1 +2 +110757 + + +2 +3 +2099 + + + + + + +kind +obj + + +12 + + +2423 +2424 +1 + + +80595 +80596 +1 + + + + + + + + +py_cmembers_versioned +21362 + + +object +1681 + + +name +8322 + + +member +15501 + + +version +2 + + + + +object +name + + +12 + + +3 +4 +59 + + +4 +5 +448 + + +5 +8 +118 + + +8 +9 +582 + + +9 +12 +154 + + +12 +20 +133 + + +20 +50 +127 + + +58 +312 +56 + + + + + + +object +member + + +12 + + +3 +4 +59 + + +4 +5 +448 + + +5 +8 +118 + + +8 +9 +591 + + +9 +12 +154 + + +12 +20 +133 + + +21 +59 +127 + + +60 +206 +47 + + + + + + +name +object + + +12 + + +1 +2 +7390 + + +2 +6 +656 + + +6 +567 +276 + + + + + + +name +member + + +12 + + +1 +2 +7407 + + +2 +6 +647 + + +6 +280 +267 + + + + + + +member +object + + +12 + + +1 +2 +14765 + + +2 +249 +736 + + + + + + +member +name + + +12 + + +1 +2 +14803 + + +2 +84 +698 + + + + + + + + +py_citems +3959 + + +object +213 + + +index +593 + + +member +1906 + + + + +object +index + + +12 + + +1 +2 +41 + + +2 +3 +37 + + +3 +4 +37 + + +4 +5 +7 + + +5 +6 +29 + + +6 +12 +16 + + +12 +22 +16 + + +24 +42 +16 + + +42 +594 +14 + + + + + + +object +member + + +12 + + +1 +2 +41 + + +2 +3 +40 + + +3 +4 +34 + + +4 +5 +20 + + +5 +6 +16 + + +6 +12 +16 + + +12 +22 +16 + + +24 +42 +16 + + +42 +546 +14 + + + + + + +index +object + + +12 + + +1 +2 +186 + + +2 +3 +62 + + +3 +4 +89 + + +4 +6 +44 + + +6 +8 +41 + + +8 +9 +83 + + +9 +14 +46 + + +14 +214 +42 + + + + + + +index +member + + +12 + + +1 +2 +186 + + +2 +3 +62 + + +3 +4 +89 + + +4 +6 +44 + + +6 +8 +41 + + +8 +9 +83 + + +9 +14 +46 + + +14 +158 +42 + + + + + + +member +object + + +12 + + +1 +2 +1112 + + +2 +3 +215 + + +3 +4 +303 + + +4 +5 +101 + + +5 +7 +166 + + +7 +21 +9 + + + + + + +member +index + + +12 + + +1 +2 +1139 + + +2 +3 +212 + + +3 +4 +298 + + +4 +5 +92 + + +5 +9 +165 + + + + + + + + +ext_argtype +6320 + + +funcid +4069 + + +arg +50 + + +typeid +466 + + + + +funcid +arg + + +12 + + +1 +2 +2726 + + +2 +3 +932 + + +3 +4 +329 + + +4 +18 +80 + + + + + + +funcid +typeid + + +12 + + +1 +2 +2694 + + +2 +3 +1149 + + +3 +6 +225 + + + + + + +arg +funcid + + +12 + + +1 +2 +23 + + +2 +3 +5 + + +3 +4 +2 + + +7 +8 +2 + + +10 +11 +2 + + +31 +32 +2 + + +141 +142 +2 + + +449 +450 +2 + + +1365 +1366 +2 + + + + + + +arg +typeid + + +12 + + +1 +2 +26 + + +2 +3 +8 + + +3 +4 +2 + + +4 +5 +2 + + +8 +9 +2 + + +12 +13 +2 + + +157 +158 +2 + + + + + + +typeid +funcid + + +12 + + +1 +2 +68 + + +2 +3 +86 + + +3 +4 +68 + + +4 +5 +38 + + +5 +6 +26 + + +6 +8 +29 + + +8 +10 +35 + + +10 +16 +41 + + +16 +22 +35 + + +24 +505 +35 + + + + + + +typeid +arg + + +12 + + +1 +2 +424 + + +2 +5 +35 + + +9 +17 +5 + + + + + + + + +ext_rettype +4719 + + +funcid +4321 + + +typeid +154 + + + + +funcid +typeid + + +12 + + +1 +2 +4042 + + +2 +11 +279 + + + + + + +typeid +funcid + + +12 + + +1 +2 +59 + + +2 +3 +14 + + +3 +4 +23 + + +4 +6 +8 + + +8 +14 +11 + + +22 +40 +11 + + +43 +115 +11 + + +116 +454 +11 + + + + + + + + +ext_proptype +398 + + +propid +386 + + +typeid +32 + + + + +propid +typeid + + +12 + + +1 +2 +374 + + +2 +3 +11 + + + + + + +typeid +propid + + +12 + + +1 +2 +11 + + +2 +3 +2 + + +7 +8 +5 + + +8 +9 +2 + + +19 +20 +2 + + +35 +36 +2 + + +52 +53 +2 + + + + + + + + +ext_argreturn +26 + + +funcid +26 + + +arg +5 + + + + +funcid +arg + + +12 + + +1 +2 +26 + + + + + + +arg +funcid + + +12 + + +2 +3 +2 + + +7 +8 +2 + + + + + + + + +py_special_objects +40 + + +obj +40 + + +name +40 + + + + +obj +name + + +12 + + +1 +2 +40 + + + + + + +name +obj + + +12 + + +1 +2 +40 + + + + + + + + +py_decorated_object +100 + + +object +100 + + +level +100 + + + + +object +level + + +12 + + + + + +level +object + + +12 + + + + + + + +xmlEncoding +100 + + +id +100 + + +encoding +100 + + + + +id +encoding + + +12 + + +1 +2 +2 + + + + + + +encoding +id + + +12 + + + + + + + +xmlDTDs +100 + + +id +100 + + +root +100 + + +publicId +100 + + +systemId +100 + + +fileid +100 + + + + +id +root + + +12 + + +1 +2 +2 + + + + + + +id +publicId + + +12 + + +1 +2 +2 + + + + + + +id +systemId + + +12 + + +1 +2 +2 + + + + + + +id +fileid + + +12 + + +1 +2 +2 + + + + + + +root +id + + +12 + + + + + +root +publicId + + +12 + + + + + +root +systemId + + +12 + + + + + +root +fileid + + +12 + + + + + +publicId +id + + +12 + + + + + +publicId +root + + +12 + + + + + +publicId +systemId + + +12 + + + + + +publicId +fileid + + +12 + + + + + +systemId +id + + +12 + + + + + +systemId +root + + +12 + + + + + +systemId +publicId + + +12 + + + + + +systemId +fileid + + +12 + + + + + +fileid +id + + +12 + + + + + +fileid +root + + +12 + + + + + +fileid +publicId + + +12 + + + + + +fileid +systemId + + +12 + + + + + + + +xmlElements +100 + + +id +100 + + +name +100 + + +parentid +100 + + +idx +100 + + +fileid +100 + + + + +id +name + + +12 + + +1 +2 +2 + + + + + + +id +parentid + + +12 + + +1 +2 +2 + + + + + + +id +idx + + +12 + + +1 +2 +2 + + + + + + +id +fileid + + +12 + + +1 +2 +2 + + + + + + +name +id + + +12 + + + + + +name +parentid + + +12 + + + + + +name +idx + + +12 + + + + + +name +fileid + + +12 + + + + + +parentid +id + + +12 + + + + + +parentid +name + + +12 + + + + + +parentid +idx + + +12 + + + + + +parentid +fileid + + +12 + + + + + +idx +id + + +12 + + + + + +idx +name + + +12 + + + + + +idx +parentid + + +12 + + + + + +idx +fileid + + +12 + + + + + +fileid +id + + +12 + + + + + +fileid +name + + +12 + + + + + +fileid +parentid + + +12 + + + + + +fileid +idx + + +12 + + + + + + + +xmlAttrs +100 + + +id +100 + + +elementid +100 + + +name +100 + + +value +100 + + +idx +100 + + +fileid +100 + + + + +id +elementid + + +12 + + +1 +2 +1 + + + + + + +id +name + + +12 + + +1 +2 +1 + + + + + + +id +value + + +12 + + +1 +2 +1 + + + + + + +id +idx + + +12 + + +1 +2 +1 + + + + + + +id +fileid + + +12 + + +1 +2 +1 + + + + + + +elementid +id + + +12 + + + + + +elementid +name + + +12 + + + + + +elementid +value + + +12 + + + + + +elementid +idx + + +12 + + + + + +elementid +fileid + + +12 + + + + + +name +id + + +12 + + + + + +name +elementid + + +12 + + + + + +name +value + + +12 + + + + + +name +idx + + +12 + + + + + +name +fileid + + +12 + + + + + +value +id + + +12 + + + + + +value +elementid + + +12 + + + + + +value +name + + +12 + + + + + +value +idx + + +12 + + + + + +value +fileid + + +12 + + + + + +idx +id + + +12 + + + + + +idx +elementid + + +12 + + + + + +idx +name + + +12 + + + + + +idx +value + + +12 + + + + + +idx +fileid + + +12 + + + + + +fileid +id + + +12 + + + + + +fileid +elementid + + +12 + + + + + +fileid +name + + +12 + + + + + +fileid +value + + +12 + + + + + +fileid +idx + + +12 + + + + + + + +xmlNs +100 + + +id +100 + + +prefixName +100 + + +URI +100 + + +fileid +100 + + + + +id +prefixName + + +12 + + + + + +id +URI + + +12 + + + + + +id +fileid + + +12 + + + + + +prefixName +id + + +12 + + + + + +prefixName +URI + + +12 + + + + + +prefixName +fileid + + +12 + + + + + +URI +id + + +12 + + + + + +URI +prefixName + + +12 + + + + + +URI +fileid + + +12 + + + + + +fileid +id + + +12 + + + + + +fileid +prefixName + + +12 + + + + + +fileid +URI + + +12 + + + + + + + +xmlHasNs +100 + + +elementId +100 + + +nsId +100 + + +fileid +100 + + + + +elementId +nsId + + +12 + + + + + +elementId +fileid + + +12 + + + + + +nsId +elementId + + +12 + + + + + +nsId +fileid + + +12 + + + + + +fileid +elementId + + +12 + + + + + +fileid +nsId + + +12 + + + + + + + +xmlComments +100 + + +id +100 + + +text +100 + + +parentid +100 + + +fileid +100 + + + + +id +text + + +12 + + +1 +2 +2 + + + + + + +id +parentid + + +12 + + +1 +2 +2 + + + + + + +id +fileid + + +12 + + +1 +2 +2 + + + + + + +text +id + + +12 + + + + + +text +parentid + + +12 + + + + + +text +fileid + + +12 + + + + + +parentid +id + + +12 + + + + + +parentid +text + + +12 + + + + + +parentid +fileid + + +12 + + + + + +fileid +id + + +12 + + + + + +fileid +text + + +12 + + + + + +fileid +parentid + + +12 + + + + + + + +xmlChars +100 + + +id +100 + + +text +100 + + +parentid +100 + + +idx +100 + + +isCDATA +100 + + +fileid +100 + + + + +id +text + + +12 + + +1 +2 +1 + + + + + + +id +parentid + + +12 + + +1 +2 +1 + + + + + + +id +idx + + +12 + + +1 +2 +1 + + + + + + +id +isCDATA + + +12 + + +1 +2 +1 + + + + + + +id +fileid + + +12 + + +1 +2 +1 + + + + + + +text +id + + +12 + + + + + +text +parentid + + +12 + + + + + +text +idx + + +12 + + + + + +text +isCDATA + + +12 + + + + + +text +fileid + + +12 + + + + + +parentid +id + + +12 + + + + + +parentid +text + + +12 + + + + + +parentid +idx + + +12 + + + + + +parentid +isCDATA + + +12 + + + + + +parentid +fileid + + +12 + + + + + +idx +id + + +12 + + + + + +idx +text + + +12 + + + + + +idx +parentid + + +12 + + + + + +idx +isCDATA + + +12 + + + + + +idx +fileid + + +12 + + + + + +isCDATA +id + + +12 + + + + + +isCDATA +text + + +12 + + + + + +isCDATA +parentid + + +12 + + + + + +isCDATA +idx + + +12 + + + + + +isCDATA +fileid + + +12 + + + + + +fileid +id + + +12 + + + + + +fileid +text + + +12 + + + + + +fileid +parentid + + +12 + + + + + +fileid +idx + + +12 + + + + + +fileid +isCDATA + + +12 + + + + + + + +xmllocations +100 + + +xmlElement +100 + + +location +100 + + + + +xmlElement +location + + +12 + + + + + +location +xmlElement + + +12 + + + + + + + + diff --git a/python/ql/src/site.qll b/python/ql/src/site.qll new file mode 100644 index 000000000000..3a3014978284 --- /dev/null +++ b/python/ql/src/site.qll @@ -0,0 +1,7 @@ +/** Site library + * + * Include predicates and classes here, if they are required to customize all analysis. + * + */ + + diff --git a/python/ql/test/.project b/python/ql/test/.project new file mode 100644 index 000000000000..8933e4b3534a --- /dev/null +++ b/python/ql/test/.project @@ -0,0 +1,18 @@ + + + python-ql-tests + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + com.semmle.plugin.qdt.core.qlnature + + diff --git a/python/ql/test/.qlpath b/python/ql/test/.qlpath new file mode 100644 index 000000000000..39a20cd34241 --- /dev/null +++ b/python/ql/test/.qlpath @@ -0,0 +1,8 @@ + + + + /semmlecode-python-queries + + /semmlecode-python-queries/semmlecode.python.dbscheme + python + diff --git a/python/ql/test/library-tests/ControlFlow/PointsToSupport/UseFromDefinition.expected b/python/ql/test/library-tests/ControlFlow/PointsToSupport/UseFromDefinition.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/PointsToSupport/UseFromDefinition.ql b/python/ql/test/library-tests/ControlFlow/PointsToSupport/UseFromDefinition.ql new file mode 100644 index 000000000000..8e3d1457572a --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/PointsToSupport/UseFromDefinition.ql @@ -0,0 +1,23 @@ +/** + * @name UseFromDefinition + * @description Insert description here... + * @kind problem + * @problem.severity warning + */ + +import python + +/*Find any Definition, assigned value pairs that 'valueForDefinition' misses */ + +Expr assignedValue(Name n) { + exists(Assign a | a.getATarget() = n and result = a.getValue()) + or + exists(Alias a | a.getAsname() = n and result = a.getValue()) +} + +from Name def, DefinitionNode d +where d = def.getAFlowNode() and + exists(assignedValue(def)) and + not d.getValue().getNode() = assignedValue(def) + +select def.toString(), assignedValue(def) \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/augassign/AugAssignFlow.expected b/python/ql/test/library-tests/ControlFlow/augassign/AugAssignFlow.expected new file mode 100644 index 000000000000..f5ab8c8a6f15 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/augassign/AugAssignFlow.expected @@ -0,0 +1,7 @@ +| ControlFlowNode for Attribute | ControlFlowNode for Attribute | 8 | +| ControlFlowNode for Attribute | ControlFlowNode for Attribute | 22 | +| ControlFlowNode for Subscript | ControlFlowNode for Subscript | 9 | +| ControlFlowNode for Subscript | ControlFlowNode for Subscript | 23 | +| ControlFlowNode for v | ControlFlowNode for v | 28 | +| ControlFlowNode for x | ControlFlowNode for x | 7 | +| ControlFlowNode for x | ControlFlowNode for x | 21 | \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/augassign/AugAssignFlow.ql b/python/ql/test/library-tests/ControlFlow/augassign/AugAssignFlow.ql new file mode 100644 index 000000000000..d356ea5de436 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/augassign/AugAssignFlow.ql @@ -0,0 +1,10 @@ +import python + +int lineof(ControlFlowNode f) { + result = f.getNode().getLocation().getStartLine() +} + +from ControlFlowNode defn, ControlFlowNode use +where defn.getNode() = use.getNode() +and defn.isStore() and use.isLoad() +select defn.toString(), use.toString(), lineof(defn) diff --git a/python/ql/test/library-tests/ControlFlow/augassign/Kind.expected b/python/ql/test/library-tests/ControlFlow/augassign/Kind.expected new file mode 100644 index 000000000000..be8d11d0adb6 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/augassign/Kind.expected @@ -0,0 +1,61 @@ +| 4 | test.py:4:5:4:9 | ControlFlowNode for func1 | store | +| 7 | test.py:7:5:7:5 | ControlFlowNode for x | aug load | +| 7 | test.py:7:5:7:5 | ControlFlowNode for x | aug store | +| 7 | test.py:7:10:7:10 | ControlFlowNode for f | load | +| 7 | test.py:7:12:7:12 | ControlFlowNode for y | load | +| 7 | test.py:7:16:7:20 | ControlFlowNode for Tuple | load | +| 8 | test.py:8:5:8:5 | ControlFlowNode for o | load | +| 8 | test.py:8:5:8:9 | ControlFlowNode for Attribute | load | +| 8 | test.py:8:5:8:14 | ControlFlowNode for Attribute | aug load | +| 8 | test.py:8:5:8:14 | ControlFlowNode for Attribute | aug store | +| 8 | test.py:8:19:8:19 | ControlFlowNode for f | load | +| 8 | test.py:8:21:8:21 | ControlFlowNode for y | load | +| 8 | test.py:8:25:8:29 | ControlFlowNode for Tuple | load | +| 9 | test.py:9:5:9:5 | ControlFlowNode for s | load | +| 9 | test.py:9:5:9:17 | ControlFlowNode for Subscript | aug load | +| 9 | test.py:9:5:9:17 | ControlFlowNode for Subscript | aug store | +| 9 | test.py:9:7:9:7 | ControlFlowNode for z | load | +| 9 | test.py:9:7:9:16 | ControlFlowNode for Tuple | load | +| 9 | test.py:9:22:9:22 | ControlFlowNode for f | load | +| 9 | test.py:9:24:9:24 | ControlFlowNode for y | load | +| 9 | test.py:9:28:9:32 | ControlFlowNode for Tuple | load | +| 10 | test.py:10:12:10:12 | ControlFlowNode for x | load | +| 13 | test.py:13:5:13:9 | ControlFlowNode for func2 | store | +| 14 | test.py:14:5:14:5 | ControlFlowNode for s | store | +| 14 | test.py:14:9:14:12 | ControlFlowNode for None | load | +| 15 | test.py:15:5:15:5 | ControlFlowNode for o | store | +| 15 | test.py:15:9:15:12 | ControlFlowNode for None | load | +| 16 | test.py:16:5:16:5 | ControlFlowNode for x | store | +| 16 | test.py:16:9:16:12 | ControlFlowNode for None | load | +| 17 | test.py:17:5:17:5 | ControlFlowNode for y | store | +| 17 | test.py:17:9:17:12 | ControlFlowNode for None | load | +| 18 | test.py:18:5:18:5 | ControlFlowNode for z | store | +| 18 | test.py:18:9:18:12 | ControlFlowNode for None | load | +| 21 | test.py:21:5:21:5 | ControlFlowNode for x | aug load | +| 21 | test.py:21:5:21:5 | ControlFlowNode for x | aug store | +| 21 | test.py:21:10:21:10 | ControlFlowNode for f | load | +| 21 | test.py:21:12:21:12 | ControlFlowNode for y | load | +| 21 | test.py:21:16:21:20 | ControlFlowNode for Tuple | load | +| 22 | test.py:22:5:22:5 | ControlFlowNode for o | load | +| 22 | test.py:22:5:22:9 | ControlFlowNode for Attribute | load | +| 22 | test.py:22:5:22:14 | ControlFlowNode for Attribute | aug load | +| 22 | test.py:22:5:22:14 | ControlFlowNode for Attribute | aug store | +| 22 | test.py:22:19:22:19 | ControlFlowNode for f | load | +| 22 | test.py:22:21:22:21 | ControlFlowNode for y | load | +| 22 | test.py:22:25:22:29 | ControlFlowNode for Tuple | load | +| 23 | test.py:23:5:23:5 | ControlFlowNode for s | load | +| 23 | test.py:23:5:23:17 | ControlFlowNode for Subscript | aug load | +| 23 | test.py:23:5:23:17 | ControlFlowNode for Subscript | aug store | +| 23 | test.py:23:7:23:7 | ControlFlowNode for z | load | +| 23 | test.py:23:7:23:16 | ControlFlowNode for Tuple | load | +| 23 | test.py:23:22:23:22 | ControlFlowNode for f | load | +| 23 | test.py:23:24:23:24 | ControlFlowNode for y | load | +| 23 | test.py:23:28:23:32 | ControlFlowNode for Tuple | load | +| 24 | test.py:24:12:24:12 | ControlFlowNode for x | load | +| 27 | test.py:27:5:27:8 | ControlFlowNode for comp | store | +| 28 | test.py:28:5:28:5 | ControlFlowNode for v | aug load | +| 28 | test.py:28:5:28:5 | ControlFlowNode for v | aug store | +| 28 | test.py:28:10:28:10 | ControlFlowNode for a | load | +| 28 | test.py:28:15:28:18 | ControlFlowNode for cond | load | +| 28 | test.py:28:25:28:25 | ControlFlowNode for b | load | +| 29 | test.py:29:12:29:12 | ControlFlowNode for v | load | diff --git a/python/ql/test/library-tests/ControlFlow/augassign/Kind.ql b/python/ql/test/library-tests/ControlFlow/augassign/Kind.ql new file mode 100644 index 000000000000..8ac3a4de0c15 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/augassign/Kind.ql @@ -0,0 +1,21 @@ + +import python + +string kind(ControlFlowNode f) { + if f.isAugLoad() then + result = "aug load" + else ( + if f.isAugStore() then + result = "aug store" + else ( + if f.isLoad() then + result = "load" + else ( + f.isStore() and result = "store" + ) + ) + ) +} + +from ControlFlowNode cfg +select cfg.getLocation().getStartLine(), cfg, kind(cfg) \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/augassign/SSA.expected b/python/ql/test/library-tests/ControlFlow/augassign/SSA.expected new file mode 100644 index 000000000000..f5c89ccdfc42 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/augassign/SSA.expected @@ -0,0 +1,3 @@ +| ControlFlowNode for v | 28 | +| ControlFlowNode for x | 7 | +| ControlFlowNode for x | 21 | \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/augassign/SSA.ql b/python/ql/test/library-tests/ControlFlow/augassign/SSA.ql new file mode 100644 index 000000000000..3738aa1f2551 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/augassign/SSA.ql @@ -0,0 +1,13 @@ +/** + * @name SSA + * @description Insert description here... + * @kind problem + * @problem.severity warning + */ + +import python + + +from ControlFlowNode defn, SsaVariable v, AugAssign a, BinaryExpr b +where v.getDefinition() = defn and a.getOperation() = b and b.contains((Expr)defn.getNode()) +select defn.toString(), defn.getNode().getLocation().getStartLine() \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/augassign/test.py b/python/ql/test/library-tests/ControlFlow/augassign/test.py new file mode 100644 index 000000000000..0a3cbe30ae38 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/augassign/test.py @@ -0,0 +1,29 @@ +#Test flow control for augmented assignment statements: + +#Parameters +def func1(s, o, x, y, z): + #Note that the right hand sides should be sufficiently complex + #to make the parts of the statement sufficiently separated + x += f(y, (1,2,3)) + o.val.item += f(y, (1,2,3)) + s[z, 10, 1:3] += f(y, (1,2,3)) + return x + +#Local variables +def func2(): + s = None + o = None + x = None + y = None + z = None + #Note that the right hand sides should be sufficiently complex + #to make the parts of the statement sufficiently separated + x += f(y, (1,2,3)) + o.val.item += f(y, (1,2,3)) + s[z, 10, 1:3] += f(y, (1,2,3)) + return x + +#Complex flow +def comp(v, cond): + v += a if cond else b + return v diff --git a/python/ql/test/library-tests/ControlFlow/comparison/Compare.expected b/python/ql/test/library-tests/ControlFlow/comparison/Compare.expected new file mode 100644 index 000000000000..34cb83503426 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/comparison/Compare.expected @@ -0,0 +1,6 @@ +| 1 | ControlFlowNode for Compare | a | b | == | +| 2 | ControlFlowNode for Compare | c | d | > | +| 3 | ControlFlowNode for Compare | e | f | < | +| 4 | ControlFlowNode for Compare | g | h | >= | +| 5 | ControlFlowNode for Compare | i | j | <= | +| 6 | ControlFlowNode for Compare | k | l | != | \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/comparison/Compare.ql b/python/ql/test/library-tests/ControlFlow/comparison/Compare.ql new file mode 100644 index 000000000000..162b02b1f612 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/comparison/Compare.ql @@ -0,0 +1,17 @@ +/** + * @name CompareTest + * @description CompareTest + * @kind problem + * @problem.severity warning + */ + +import python + +from CompareNode c, NameNode l, NameNode r, Cmpop op, int line, Variable vl, Variable vr +where c.operands(l, op, r) and +line = c.getLocation().getStartLine() and +line = l.getLocation().getStartLine() and +line = r.getLocation().getStartLine() and +l.uses(vl) and r.uses(vr) +select line, c.toString(), vl.getId(), vr.getId(), op.getSymbol() + diff --git a/python/ql/test/library-tests/ControlFlow/comparison/compare.py b/python/ql/test/library-tests/ControlFlow/comparison/compare.py new file mode 100644 index 000000000000..e40411d931f6 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/comparison/compare.py @@ -0,0 +1,6 @@ +a == b +c > d +e < f +g >= h +i <= j +k != l diff --git a/python/ql/test/library-tests/ControlFlow/delete/test.expected b/python/ql/test/library-tests/ControlFlow/delete/test.expected new file mode 100644 index 000000000000..ccbfba0b2a8a --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/delete/test.expected @@ -0,0 +1,16 @@ +| 0 | Entry node for Module test | 1 | ControlFlowNode for FunctionExpr | +| 1 | ControlFlowNode for FunctionExpr | 1 | ControlFlowNode for foo | +| 1 | ControlFlowNode for a | 1 | ControlFlowNode for b | +| 1 | ControlFlowNode for b | 1 | ControlFlowNode for c | +| 1 | ControlFlowNode for c | 2 | ControlFlowNode for a | +| 1 | ControlFlowNode for foo | 0 | Exit node for Module test | +| 1 | Entry node for Function foo | 1 | ControlFlowNode for a | +| 2 | ControlFlowNode for Attribute | 2 | ControlFlowNode for Delete | +| 2 | ControlFlowNode for Delete | 3 | ControlFlowNode for a | +| 2 | ControlFlowNode for a | 2 | ControlFlowNode for Attribute | +| 3 | ControlFlowNode for Delete | 3 | ControlFlowNode for b | +| 3 | ControlFlowNode for Delete | 4 | ControlFlowNode for c | +| 3 | ControlFlowNode for a | 3 | ControlFlowNode for Delete | +| 3 | ControlFlowNode for b | 3 | ControlFlowNode for Delete | +| 4 | ControlFlowNode for Delete | 1 | Exit node for Function foo | +| 4 | ControlFlowNode for c | 4 | ControlFlowNode for Delete | diff --git a/python/ql/test/library-tests/ControlFlow/delete/test.py b/python/ql/test/library-tests/ControlFlow/delete/test.py new file mode 100644 index 000000000000..820bc3392d3b --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/delete/test.py @@ -0,0 +1,4 @@ +def foo(a, b, c): + del a.x + del a, b + del c diff --git a/python/ql/test/library-tests/ControlFlow/delete/test.ql b/python/ql/test/library-tests/ControlFlow/delete/test.ql new file mode 100644 index 000000000000..517733b70d63 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/delete/test.ql @@ -0,0 +1,5 @@ +import python + +from ControlFlowNode p, ControlFlowNode s +where p.getASuccessor() = s +select p.getLocation().getStartLine().toString(), p.toString(), s.getLocation().getStartLine(), s.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/dominators/DominatesSanity.expected b/python/ql/test/library-tests/ControlFlow/dominators/DominatesSanity.expected new file mode 100644 index 000000000000..fe98b1555877 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/dominators/DominatesSanity.expected @@ -0,0 +1 @@ +| 0 | 0 | \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/dominators/DominatesSanity.ql b/python/ql/test/library-tests/ControlFlow/dominators/DominatesSanity.ql new file mode 100644 index 000000000000..cb53879e63bc --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/dominators/DominatesSanity.ql @@ -0,0 +1,9 @@ + +import python + +select count(BasicBlock b1, BasicBlock b2 +| b1 = b2.getImmediateDominator+() and not b1.strictlyDominates(b2) +), +count(BasicBlock b1, BasicBlock b2 +| not b1 = b2.getImmediateDominator+() and b1.strictlyDominates(b2) +) diff --git a/python/ql/test/library-tests/ControlFlow/dominators/idom.expected b/python/ql/test/library-tests/ControlFlow/dominators/idom.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/dominators/idom.ql b/python/ql/test/library-tests/ControlFlow/dominators/idom.ql new file mode 100644 index 000000000000..37739501bd93 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/dominators/idom.ql @@ -0,0 +1,16 @@ +/** + * @name Check all non-scope nodes have an immediate dominator + * @description Check all non-scope nodes have an immediate dominator + * @kind problem + * @problem.severity warning + */ + +import python + +/* This query should *never* produce a result */ + +from ControlFlowNode f +where not exists(f.getImmediateDominator()) +and not f.getNode() instanceof Scope +select f + diff --git a/python/ql/test/library-tests/ControlFlow/dominators/test.py b/python/ql/test/library-tests/ControlFlow/dominators/test.py new file mode 100644 index 000000000000..fe9db8fad0a0 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/dominators/test.py @@ -0,0 +1,37 @@ + + +def f(): + while 0: + pass + return 1 + +def g(): + while 1: + pass + return unreachable + +def h(x): + if x: + return x + else: + return None + + + + +def k(a, b): + for x in a or b: + pass + return 0 + +def l(a, b, c): + if a or b or c: + return a or b or c + else: + return None + +def m(a, b, c): + if a and b and c: + return a and b and c + else: + return None \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/except/test.expected b/python/ql/test/library-tests/ControlFlow/except/test.expected new file mode 100644 index 000000000000..da4b21d23dfa --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/except/test.expected @@ -0,0 +1,9 @@ +| e2 | +| e3 | +| e5 | +| e6 | +| e8 | +| ec | +| ed | +| ee | +| ef | \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/except/test.py b/python/ql/test/library-tests/ControlFlow/except/test.py new file mode 100644 index 000000000000..e66082c4b88a --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/except/test.py @@ -0,0 +1,106 @@ +#Ensure there is an exceptional edge from the following case + + + + + + +def f2(): + b, d = Base, Derived + try: + class MyNewClass(b, d): + pass + except: + e2 + +def f3(): + sequence_of_four = a_global + try: + a, b, c = sequence_of_four + except: + e3 + +#Always treat locals as non-raising to keep DB size down. +def f4(): + if cond: + local = 1 + try: + local + except: + e4 + +def f5(): + try: + a_global + except: + e5 + +def f6(): + local = a_global + try: + local() + except: + e6 + +#Literals can't raise +def f7(): + try: + 4 + except: + e7 + +def f8(): + try: + a + b + except: + e8 + + +#OK assignments +def f9(): + try: + a, b = 1, 2 + except: + e9 + +def fa(): + seq = a_global + try: + a = seq + except: + ea + +def fb(): + a, b, c = a_global + try: + seq = a, b, c + except: + eb + +#Ensure that a.b and c[d] can raise + +def fc(): + a, b = a_global + try: + return a[b] + except: + ec + +def fd(): + a = a_global + try: + return a.b + except: + ed + + +def fe(): + try: + call() + except: + ee + else: + ef + + + diff --git a/python/ql/test/library-tests/ControlFlow/except/test.ql b/python/ql/test/library-tests/ControlFlow/except/test.ql new file mode 100644 index 000000000000..a81459377af2 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/except/test.ql @@ -0,0 +1,5 @@ +import python + +from GlobalVariable v, Name n, ControlFlowNode f +where v.getId().charAt(0) = "e" and n.uses(v) and f.getNode() = n +select v.getId() diff --git a/python/ql/test/library-tests/ControlFlow/general/Comments.expected b/python/ql/test/library-tests/ControlFlow/general/Comments.expected new file mode 100644 index 000000000000..f55daf3bc72a --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/general/Comments.expected @@ -0,0 +1 @@ +| Module flowtest | 5 | diff --git a/python/ql/test/library-tests/ControlFlow/general/Comments.ql b/python/ql/test/library-tests/ControlFlow/general/Comments.ql new file mode 100644 index 000000000000..e93c8aae330a --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/general/Comments.ql @@ -0,0 +1,6 @@ +import python + +from Module m, int n +where n = m.getMetrics().getNumberOfLinesOfComments() +select m.toString(), n + diff --git a/python/ql/test/library-tests/ControlFlow/general/Cyclo.expected b/python/ql/test/library-tests/ControlFlow/general/Cyclo.expected new file mode 100644 index 000000000000..e814047bf80d --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/general/Cyclo.expected @@ -0,0 +1,9 @@ +| Function definitions | 2 | +| Function deletion | 1 | +| Function gloop | 2 | +| Function if_else | 2 | +| Function lloop | 2 | +| Function normal_args | 1 | +| Function special_args | 1 | +| Function try_except | 4 | +| Function try_finally | 2 | \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/general/Cyclo.ql b/python/ql/test/library-tests/ControlFlow/general/Cyclo.ql new file mode 100644 index 000000000000..6ca0327ab0b7 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/general/Cyclo.ql @@ -0,0 +1,6 @@ +import python + +from Function func +select func.toString(), func.getMetrics().getCyclomaticComplexity() + + diff --git a/python/ql/test/library-tests/ControlFlow/general/ImmediateDominatorCheck.expected b/python/ql/test/library-tests/ControlFlow/general/ImmediateDominatorCheck.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/general/ImmediateDominatorCheck.ql b/python/ql/test/library-tests/ControlFlow/general/ImmediateDominatorCheck.ql new file mode 100644 index 000000000000..f038fd8d77a7 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/general/ImmediateDominatorCheck.ql @@ -0,0 +1,16 @@ + + +import python + +predicate +can_reach_from_entry_without_passing(ControlFlowNode target, ControlFlowNode pass) { + target != pass and target.getScope() = pass.getScope() and + (target.isEntryNode() or + exists(ControlFlowNode pre | target.getAPredecessor() = pre and can_reach_from_entry_without_passing(pre, pass))) +} + +from ControlFlowNode node, ControlFlowNode dom +where dom = node.getImmediateDominator() +and +can_reach_from_entry_without_passing(node, dom) +select node.toString(), dom.toString() diff --git a/python/ql/test/library-tests/ControlFlow/general/Lines.expected b/python/ql/test/library-tests/ControlFlow/general/Lines.expected new file mode 100644 index 000000000000..b7c1e0393feb --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/general/Lines.expected @@ -0,0 +1,10 @@ +| Function definitions | 13 | +| Function deletion | 7 | +| Function gloop | 6 | +| Function if_else | 6 | +| Function lloop | 4 | +| Function normal_args | 3 | +| Function special_args | 4 | +| Function try_except | 7 | +| Function try_finally | 7 | +| Module flowtest | 70 | diff --git a/python/ql/test/library-tests/ControlFlow/general/Lines.ql b/python/ql/test/library-tests/ControlFlow/general/Lines.ql new file mode 100644 index 000000000000..60046ef32420 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/general/Lines.ql @@ -0,0 +1,7 @@ +import python + +from Scope s, int n +where exists(Function f | f = s | n = f.getMetrics().getNumberOfLines()) or +exists(Module m | m = s | n = m.getMetrics().getNumberOfLines()) +select s.toString(), n + diff --git a/python/ql/test/library-tests/ControlFlow/general/Reaches.expected b/python/ql/test/library-tests/ControlFlow/general/Reaches.expected new file mode 100644 index 000000000000..4fc0324ad13c --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/general/Reaches.expected @@ -0,0 +1,19 @@ +| ExceptionL | +| ExceptionR | +| cond1 | +| cond2 | +| except_handler | +| fall_through1 | +| fall_through2 | +| final_body | +| g1 | +| g2 | +| g3 | +| g5 | +| left | +| merged | +| right | +| try_body1 | +| try_body2 | +| try_body3 | +| try_body4 | \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/general/Reaches.ql b/python/ql/test/library-tests/ControlFlow/general/Reaches.ql new file mode 100644 index 000000000000..548be578a762 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/general/Reaches.ql @@ -0,0 +1,13 @@ +import python + +predicate reaches_exit(Name u) { + u.uses(_) and + exists(ControlFlowNode f, BasicBlock b | + f.getNode() = u and f.getBasicBlock() = b | + b.reachesExit() + ) +} + +from Name u +where reaches_exit(u) and u.getVariable() instanceof GlobalVariable +select u.toString() diff --git a/python/ql/test/library-tests/ControlFlow/general/flowtest.py b/python/ql/test/library-tests/ControlFlow/general/flowtest.py new file mode 100644 index 000000000000..985895574090 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/general/flowtest.py @@ -0,0 +1,73 @@ +def definitions(p1): # Multiple defns of same variable + v1 = 0 + v1 + v1 = 1 + v1 + v2 = 2 + if p1: + v1 = 1 + v2 = 2 + else: + v2 = 2 + v1 + v2 + +def lloop(): #Loop + v3 = 0 + while 1: + v3 + +def gloop(): #Loop and global + global d1 + d1 = 0 + g1 + while g2: + g3 + +def deletion(): + global g4 + del g4 + g5 + v4 = 0 + del v4 + v4 + +def if_else(): + if cond1: + left + else: + right + merged + +def try_except(): + try: + try_body1() + try_body2() + except ExceptionL if cond2 else ExceptionR: + except_handler + fall_through1 + +def try_finally(): + try: + try_body3() + try_body4() + finally: + final_body + fall_through2 + +def normal_args(arg0, arg1): + arg0 + arg1 + +def special_args(*vararg, **kwarg): + vararg + + kwarg + + + +#A comment +#at the end of the file + + + diff --git a/python/ql/test/library-tests/ControlFlow/pruning/test.expected b/python/ql/test/library-tests/ControlFlow/pruning/test.expected new file mode 100644 index 000000000000..b8cd5b6b98d7 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/pruning/test.expected @@ -0,0 +1,45 @@ +| 5 | 0 | +| 11 | 0 | +| 18 | 1 | +| 24 | 0 | +| 25 | 1 | +| 29 | 1 | +| 30 | 0 | +| 32 | 1 | +| 33 | 1 | +| 34 | 1 | +| 35 | 1 | +| 37 | 0 | +| 38 | 1 | +| 39 | 0 | +| 40 | 1 | +| 48 | 0 | +| 49 | 1 | +| 51 | 1 | +| 52 | 1 | +| 61 | 1 | +| 64 | 1 | +| 72 | 1 | +| 75 | 1 | +| 82 | 1 | +| 94 | 0 | +| 96 | 0 | +| 103 | 0 | +| 112 | 1 | +| 114 | 1 | +| 120 | 0 | +| 127 | 1 | +| 131 | 0 | +| 133 | 1 | +| 135 | 0 | +| 137 | 1 | +| 139 | 1 | +| 141 | 0 | +| 143 | 0 | +| 145 | 1 | +| 147 | 1 | +| 149 | 0 | +| 152 | 0 | +| 154 | 1 | +| 161 | 1 | +| 163 | 1 | diff --git a/python/ql/test/library-tests/ControlFlow/pruning/test.py b/python/ql/test/library-tests/ControlFlow/pruning/test.py new file mode 100644 index 000000000000..bdc43c414ad5 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/pruning/test.py @@ -0,0 +1,164 @@ +#Test number of CFG nodes for each use of 'count' + +def dead(): + return 0 + count + +def conditional_dead(test): + if test: + return + if test: + count + +def made_true(seq): + if seq: + return + seq.append(1) + if seq: + count + +def boolop(t1, t2, t3, t4, t5, t6): + if not t1: + return + #bool(t1) must be True + t1 or count + t1 and count + if t2: + return + #bool(t2) must be False + t2 or count + t2 and count + if t3 or t4: + t3 or count + t3 and count + t3 or count + t4 and count + if t5 and t6: + t5 or count + t5 and count + t6 or count + t6 and count + +def with_splitting(t1, t2): + if t1: + if not t2: + return + #Cannot have bool(t1) be True and bool(t2) be False + if t1: + t2 or count #Unreachable + t2 and count + else: + t2 or count + t2 and count + +def loops(seq1, seq2, seq3, seq4, seq5): + if seq1: + return + if not seq2: + return + #bool(seq1) is False; bool(seq2) is True + while seq1: + count #This is unreachable, but the pop below forces us to be conservative. + seq1.pop() + while seq2: + count + seq2.pop() + if seq3: + return + if not seq4: + return + #bool(seq3) is False; bool(seq4) is True + for var in seq3: + count #This is unreachable, but we cannot infer this yet. + print(var) + for var in seq4: + count + print(var) + #seq5 false then made true + if seq5: + return + seq5.append(1) + for var in seq5: + count + print(var) + +#Logic does not apply to global variables in calls, +#as they may be changed from true to false externally. +from some_module import x, y +if not x: + raise Exception() +if y: + raise Exception() +make_a_call() +if not x: + count +if y: + count + +# However, modules are always true -- Which is important. +import another_module + +make_a_call() +if not another_module: + count + + +def negated_conditional_live(t1, t2): + if not t1: + return + if t2: + return + if t1: + count + if not t2: + count + +def negated_conditional_dead(test): + if not test: + return + if not test: + count + +def made_true2(m): + if m: + return + del m['a'] + if m: + count + +def prune_const_branches(): + if None: + count + if not None: + count + if False: + count + else: + count + if True: + count + else: + count + if 0: + count + else: + count + if -4: + count + else: + count + #Muliptle nots + if not not False: + count + if not not not False: + count + +#ODASA-6794 +def attribute_lookup_cannot_effect_comparisons_with_immutable_constants(ps): + if ps is not None: + ps_clamped = ps.clamp() + if ps is None: + count + else: + count + diff --git a/python/ql/test/library-tests/ControlFlow/pruning/test.ql b/python/ql/test/library-tests/ControlFlow/pruning/test.ql new file mode 100644 index 000000000000..837228fcd2ac --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/pruning/test.ql @@ -0,0 +1,5 @@ +import python + +from Name n +where n.getId() = "count" +select n.getLocation().getStartLine(), count(n.getAFlowNode()) diff --git a/python/ql/test/library-tests/ControlFlow/raising_stmts/RaisingFlow.expected b/python/ql/test/library-tests/ControlFlow/raising_stmts/RaisingFlow.expected new file mode 100644 index 000000000000..ed831c699678 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/raising_stmts/RaisingFlow.expected @@ -0,0 +1,99 @@ +| 5 | ControlFlowNode for ImportExpr | normal | 5 | test.py:5:1:5:16 | ControlFlowNode for from _s import * | +| 5 | ControlFlowNode for from _s import * | normal | 7 | test.py:7:1:7:8 | ControlFlowNode for FunctionExpr | +| 7 | ControlFlowNode for FunctionExpr | normal | 7 | test.py:7:5:7:5 | ControlFlowNode for f | +| 7 | ControlFlowNode for f | normal | 15 | test.py:15:1:15:8 | ControlFlowNode for FunctionExpr | +| 8 | ControlFlowNode for ImportExpr | normal | 8 | test.py:8:12:8:12 | ControlFlowNode for x | +| 8 | ControlFlowNode for x | normal | 9 | test.py:9:10:9:10 | ControlFlowNode for ImportExpr | +| 9 | ControlFlowNode for ImportExpr | normal | 9 | test.py:9:19:9:19 | ControlFlowNode for ImportMember | +| 9 | ControlFlowNode for ImportMember | normal | 9 | test.py:9:19:9:19 | ControlFlowNode for b | +| 9 | ControlFlowNode for b | normal | 10 | test.py:10:9:10:12 | ControlFlowNode for AA_s | +| 10 | ControlFlowNode for AA_s | normal | 10 | test.py:10:14:10:14 | ControlFlowNode for IntegerLiteral | +| 10 | ControlFlowNode for Delete | normal | 12 | test.py:12:18:12:21 | ControlFlowNode for AA_p | +| 10 | ControlFlowNode for IntegerLiteral | normal | 10 | test.py:10:9:10:15 | ControlFlowNode for Subscript | +| 10 | ControlFlowNode for Subscript | normal | 10 | test.py:10:5:10:15 | ControlFlowNode for Delete | +| 12 | ControlFlowNode for AA_p | normal | 12 | test.py:12:5:12:14 | ControlFlowNode for Tuple | +| 12 | ControlFlowNode for AA_q | normal | 12 | test.py:12:11:12:14 | ControlFlowNode for AA_r | +| 12 | ControlFlowNode for AA_r | normal | 13 | test.py:13:12:13:15 | ControlFlowNode for AA_c | +| 12 | ControlFlowNode for Tuple | normal | 12 | test.py:12:5:12:8 | ControlFlowNode for AA_q | +| 13 | ControlFlowNode for AA_c | normal | 13 | test.py:13:20:13:23 | ControlFlowNode for AA_d | +| 13 | ControlFlowNode for AA_d | normal | 13 | test.py:13:12:13:23 | ControlFlowNode for Compare | +| 13 | ControlFlowNode for Compare | normal | 13 | test.py:13:5:13:23 | ControlFlowNode for Assert | +| 13 | ControlFlowNode for Compare | normal | 13 | test.py:13:5:13:23 | ControlFlowNode for Assert | +| 15 | ControlFlowNode for FunctionExpr | normal | 15 | test.py:15:5:15:5 | ControlFlowNode for g | +| 15 | ControlFlowNode for g | normal | 26 | test.py:26:1:26:8 | ControlFlowNode for FunctionExpr | +| 16 | ControlFlowNode for Try | normal | 17 | test.py:17:16:17:16 | ControlFlowNode for ImportExpr | +| 17 | ControlFlowNode for ImportExpr | normal | 17 | test.py:17:16:17:16 | ControlFlowNode for x | +| 17 | ControlFlowNode for ImportExpr | exception | 23 | test.py:23:5:23:11 | ControlFlowNode for ExceptStmt | +| 17 | ControlFlowNode for x | normal | 18 | test.py:18:14:18:14 | ControlFlowNode for ImportExpr | +| 18 | ControlFlowNode for ImportExpr | normal | 18 | test.py:18:23:18:23 | ControlFlowNode for ImportMember | +| 18 | ControlFlowNode for ImportExpr | exception | 23 | test.py:23:5:23:11 | ControlFlowNode for ExceptStmt | +| 18 | ControlFlowNode for ImportMember | normal | 18 | test.py:18:23:18:23 | ControlFlowNode for b | +| 18 | ControlFlowNode for ImportMember | exception | 23 | test.py:23:5:23:11 | ControlFlowNode for ExceptStmt | +| 18 | ControlFlowNode for b | normal | 19 | test.py:19:13:19:16 | ControlFlowNode for AA_s | +| 19 | ControlFlowNode for AA_s | normal | 19 | test.py:19:18:19:18 | ControlFlowNode for IntegerLiteral | +| 19 | ControlFlowNode for AA_s | exception | 23 | test.py:23:5:23:11 | ControlFlowNode for ExceptStmt | +| 19 | ControlFlowNode for Delete | normal | 21 | test.py:21:22:21:25 | ControlFlowNode for AA_p | +| 19 | ControlFlowNode for IntegerLiteral | normal | 19 | test.py:19:13:19:19 | ControlFlowNode for Subscript | +| 19 | ControlFlowNode for Subscript | normal | 19 | test.py:19:9:19:19 | ControlFlowNode for Delete | +| 19 | ControlFlowNode for Subscript | exception | 23 | test.py:23:5:23:11 | ControlFlowNode for ExceptStmt | +| 21 | ControlFlowNode for AA_p | normal | 21 | test.py:21:9:21:18 | ControlFlowNode for Tuple | +| 21 | ControlFlowNode for AA_p | exception | 23 | test.py:23:5:23:11 | ControlFlowNode for ExceptStmt | +| 21 | ControlFlowNode for AA_q | normal | 21 | test.py:21:15:21:18 | ControlFlowNode for AA_r | +| 21 | ControlFlowNode for AA_r | normal | 22 | test.py:22:16:22:19 | ControlFlowNode for AA_c | +| 21 | ControlFlowNode for Tuple | normal | 21 | test.py:21:9:21:12 | ControlFlowNode for AA_q | +| 21 | ControlFlowNode for Tuple | exception | 23 | test.py:23:5:23:11 | ControlFlowNode for ExceptStmt | +| 22 | ControlFlowNode for AA_c | normal | 22 | test.py:22:24:22:27 | ControlFlowNode for AA_d | +| 22 | ControlFlowNode for AA_c | exception | 23 | test.py:23:5:23:11 | ControlFlowNode for ExceptStmt | +| 22 | ControlFlowNode for AA_d | normal | 22 | test.py:22:16:22:27 | ControlFlowNode for Compare | +| 22 | ControlFlowNode for AA_d | exception | 23 | test.py:23:5:23:11 | ControlFlowNode for ExceptStmt | +| 22 | ControlFlowNode for Assert | exception | 23 | test.py:23:5:23:11 | ControlFlowNode for ExceptStmt | +| 22 | ControlFlowNode for Compare | normal | 22 | test.py:22:9:22:27 | ControlFlowNode for Assert | +| 22 | ControlFlowNode for Compare | normal | 22 | test.py:22:9:22:27 | ControlFlowNode for Assert | +| 23 | ControlFlowNode for ExceptStmt | normal | 24 | test.py:24:9:24:12 | ControlFlowNode for Pass | +| 26 | ControlFlowNode for FunctionExpr | normal | 26 | test.py:26:5:26:5 | ControlFlowNode for h | +| 26 | ControlFlowNode for h | normal | 34 | test.py:34:1:34:8 | ControlFlowNode for FunctionExpr | +| 27 | ControlFlowNode for Try | normal | 28 | test.py:28:9:28:14 | ControlFlowNode for a_call | +| 28 | ControlFlowNode for a_call | normal | 28 | test.py:28:9:28:16 | ControlFlowNode for a_call() | +| 28 | ControlFlowNode for a_call | exception | 29 | test.py:29:5:29:11 | ControlFlowNode for ExceptStmt | +| 28 | ControlFlowNode for a_call() | normal | 32 | test.py:32:9:32:12 | ControlFlowNode for Pass | +| 28 | ControlFlowNode for a_call() | exception | 29 | test.py:29:5:29:11 | ControlFlowNode for ExceptStmt | +| 29 | ControlFlowNode for ExceptStmt | normal | 30 | test.py:30:16:30:19 | ControlFlowNode for AA_c | +| 30 | ControlFlowNode for AA_c | normal | 30 | test.py:30:24:30:27 | ControlFlowNode for AA_d | +| 30 | ControlFlowNode for AA_c | exception | 32 | test.py:32:9:32:12 | ControlFlowNode for Pass | +| 30 | ControlFlowNode for AA_d | normal | 30 | test.py:30:16:30:27 | ControlFlowNode for Compare | +| 30 | ControlFlowNode for AA_d | exception | 32 | test.py:32:9:32:12 | ControlFlowNode for Pass | +| 30 | ControlFlowNode for Assert | normal | 32 | test.py:32:9:32:12 | ControlFlowNode for Pass | +| 30 | ControlFlowNode for Assert | exception | 32 | test.py:32:9:32:12 | ControlFlowNode for Pass | +| 30 | ControlFlowNode for Compare | normal | 30 | test.py:30:9:30:27 | ControlFlowNode for Assert | +| 30 | ControlFlowNode for Compare | normal | 30 | test.py:30:9:30:27 | ControlFlowNode for Assert | +| 34 | ControlFlowNode for FunctionExpr | normal | 34 | test.py:34:5:34:5 | ControlFlowNode for k | +| 34 | ControlFlowNode for k | normal | 45 | test.py:45:1:45:19 | ControlFlowNode for FunctionExpr | +| 35 | ControlFlowNode for Try | normal | 37 | test.py:37:13:37:13 | ControlFlowNode for y | +| 37 | ControlFlowNode for x | normal | 38 | test.py:38:16:38:16 | ControlFlowNode for a | +| 37 | ControlFlowNode for y | normal | 37 | test.py:37:9:37:9 | ControlFlowNode for x | +| 37 | ControlFlowNode for y | exception | 41 | test.py:41:5:41:11 | ControlFlowNode for ExceptStmt | +| 38 | ControlFlowNode for Tuple | normal | 38 | test.py:38:9:38:9 | ControlFlowNode for c | +| 38 | ControlFlowNode for Tuple | normal | 38 | test.py:38:9:38:12 | ControlFlowNode for Tuple | +| 38 | ControlFlowNode for a | normal | 38 | test.py:38:19:38:19 | ControlFlowNode for b | +| 38 | ControlFlowNode for a | exception | 41 | test.py:41:5:41:11 | ControlFlowNode for ExceptStmt | +| 38 | ControlFlowNode for b | normal | 38 | test.py:38:16:38:19 | ControlFlowNode for Tuple | +| 38 | ControlFlowNode for b | exception | 41 | test.py:41:5:41:11 | ControlFlowNode for ExceptStmt | +| 38 | ControlFlowNode for c | normal | 38 | test.py:38:12:38:12 | ControlFlowNode for d | +| 38 | ControlFlowNode for d | normal | 40 | test.py:40:16:40:16 | ControlFlowNode for p | +| 40 | ControlFlowNode for Tuple | normal | 40 | test.py:40:9:40:9 | ControlFlowNode for q | +| 40 | ControlFlowNode for Tuple | exception | 41 | test.py:41:5:41:11 | ControlFlowNode for ExceptStmt | +| 40 | ControlFlowNode for p | normal | 40 | test.py:40:9:40:12 | ControlFlowNode for Tuple | +| 40 | ControlFlowNode for p | exception | 41 | test.py:41:5:41:11 | ControlFlowNode for ExceptStmt | +| 40 | ControlFlowNode for q | normal | 40 | test.py:40:12:40:12 | ControlFlowNode for r | +| 41 | ControlFlowNode for ExceptStmt | normal | 42 | test.py:42:9:42:12 | ControlFlowNode for Pass | +| 45 | ControlFlowNode for FunctionExpr | normal | 45 | test.py:45:5:45:13 | ControlFlowNode for odasa3686 | +| 45 | ControlFlowNode for obj | normal | 47 | test.py:47:9:47:12 | ControlFlowNode for Try | +| 47 | ControlFlowNode for Try | normal | 48 | test.py:48:13:48:16 | ControlFlowNode for None | +| 48 | ControlFlowNode for Compare | normal | 49 | test.py:49:20:49:23 | ControlFlowNode for True | +| 48 | ControlFlowNode for Compare | exception | 50 | test.py:50:9:50:25 | ControlFlowNode for ExceptStmt | +| 48 | ControlFlowNode for None | normal | 48 | test.py:48:21:48:23 | ControlFlowNode for obj | +| 48 | ControlFlowNode for obj | normal | 48 | test.py:48:13:48:23 | ControlFlowNode for Compare | +| 49 | ControlFlowNode for True | normal | 49 | test.py:49:13:49:23 | ControlFlowNode for Return | +| 50 | ControlFlowNode for ExceptStmt | normal | 50 | test.py:50:16:50:24 | ControlFlowNode for TypeError | +| 50 | ControlFlowNode for TypeError | normal | 51 | test.py:51:20:51:24 | ControlFlowNode for False | +| 51 | ControlFlowNode for False | normal | 51 | test.py:51:13:51:24 | ControlFlowNode for Return | diff --git a/python/ql/test/library-tests/ControlFlow/raising_stmts/RaisingFlow.ql b/python/ql/test/library-tests/ControlFlow/raising_stmts/RaisingFlow.ql new file mode 100644 index 000000000000..bfc884f7bacd --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/raising_stmts/RaisingFlow.ql @@ -0,0 +1,17 @@ +/** + * @name Raising Flow + * @description Test + */ + +import python + +from ControlFlowNode p, ControlFlowNode s, string kind +where p.getASuccessor() = s and +(if s = p.getAnExceptionalSuccessor() then + kind = "exception" + else + kind = " normal " +) and +not p.getNode() instanceof Scope and +not s.getNode() instanceof Scope +select p.getNode().getLocation().getStartLine(), p.toString(), kind, s.getNode().getLocation().getStartLine(), s diff --git a/python/ql/test/library-tests/ControlFlow/raising_stmts/test.py b/python/ql/test/library-tests/ControlFlow/raising_stmts/test.py new file mode 100644 index 000000000000..42e8baa47cbe --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/raising_stmts/test.py @@ -0,0 +1,51 @@ +#All variables that will evaluate before statements start with "AA_" +#so that the test output better reflects the execution order and makes +#it easier to manually verify. + +from _s import * + +def f(): + import x + from a import b + del AA_s[0] + + AA_q, AA_r = AA_p + assert AA_c == AA_d + +def g(): + try: + import x + from a import b + del AA_s[0] + + AA_q, AA_r = AA_p + assert AA_c == AA_d + except: + pass + +def h(): + try: + a_call() + except: + assert AA_c == AA_d + finally: + pass + +def k(): + try: + #Safe + x = y + c, d = a, b + #Unsafe + q, r = p + except: + pass + + +def odasa3686(obj): + #Test for iterability + try: + None in obj + return True + except TypeError: + return False diff --git a/python/ql/test/library-tests/ControlFlow/splitting/NodeCount.expected b/python/ql/test/library-tests/ControlFlow/splitting/NodeCount.expected new file mode 100644 index 000000000000..c1ce91c2e8da --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/splitting/NodeCount.expected @@ -0,0 +1,565 @@ +| 2 | split1 | test.py:2:12:2:15 | cond | 1 | +| 3 | split1 | test.py:3:8:3:11 | cond | 1 | +| 4 | split1 | test.py:4:9:4:12 | Pass | 1 | +| 5 | split1 | test.py:5:8:5:11 | cond | 2 | +| 6 | split1 | test.py:6:9:6:12 | Pass | 1 | +| 8 | dont_split1 | test.py:8:17:8:20 | cond | 1 | +| 9 | dont_split1 | test.py:9:8:9:11 | cond | 1 | +| 10 | dont_split1 | test.py:10:9:10:12 | Pass | 1 | +| 11 | dont_split1 | test.py:11:5:11:8 | cond | 1 | +| 11 | dont_split1 | test.py:11:12:11:12 | f | 1 | +| 11 | dont_split1 | test.py:11:12:11:14 | f() | 1 | +| 12 | dont_split1 | test.py:12:8:12:11 | cond | 1 | +| 13 | dont_split1 | test.py:13:9:13:12 | Pass | 1 | +| 15 | dont_split2 | test.py:15:17:15:20 | cond | 1 | +| 16 | dont_split2 | test.py:16:8:16:11 | cond | 1 | +| 17 | dont_split2 | test.py:17:9:17:12 | Pass | 1 | +| 18 | dont_split2 | test.py:18:5:18:20 | For | 1 | +| 18 | dont_split2 | test.py:18:9:18:12 | cond | 1 | +| 18 | dont_split2 | test.py:18:17:18:19 | seq | 1 | +| 18 | dont_split2 | test.py:18:22:18:25 | Pass | 1 | +| 19 | dont_split2 | test.py:19:8:19:11 | cond | 1 | +| 20 | dont_split2 | test.py:20:9:20:12 | Pass | 1 | +| 24 | split2 | test.py:24:5:24:8 | Try | 1 | +| 25 | split2 | test.py:25:9:25:12 | call | 1 | +| 25 | split2 | test.py:25:9:25:14 | call() | 1 | +| 26 | split2 | test.py:26:9:26:9 | x | 1 | +| 26 | split2 | test.py:26:13:26:16 | True | 1 | +| 27 | split2 | test.py:27:5:27:11 | ExceptStmt | 1 | +| 28 | split2 | test.py:28:9:28:9 | x | 1 | +| 28 | split2 | test.py:28:13:28:17 | False | 1 | +| 29 | split2 | test.py:29:8:29:8 | x | 2 | +| 30 | split2 | test.py:30:9:30:12 | Pass | 1 | +| 33 | unclear_split3 | test.py:33:5:33:8 | Try | 1 | +| 34 | unclear_split3 | test.py:34:9:34:12 | call | 1 | +| 34 | unclear_split3 | test.py:34:9:34:14 | call() | 1 | +| 35 | unclear_split3 | test.py:35:9:35:9 | x | 1 | +| 35 | unclear_split3 | test.py:35:13:35:16 | True | 1 | +| 36 | unclear_split3 | test.py:36:5:36:11 | ExceptStmt | 1 | +| 37 | unclear_split3 | test.py:37:9:37:9 | x | 1 | +| 37 | unclear_split3 | test.py:37:13:37:17 | False | 1 | +| 38 | unclear_split3 | test.py:38:8:38:11 | cond | 1 | +| 39 | unclear_split3 | test.py:39:9:39:9 | x | 1 | +| 39 | unclear_split3 | test.py:39:13:39:17 | False | 1 | +| 40 | unclear_split3 | test.py:40:8:40:8 | x | 2 | +| 41 | unclear_split3 | test.py:41:9:41:12 | Pass | 1 | +| 43 | split4 | test.py:43:12:43:12 | x | 1 | +| 44 | split4 | test.py:44:8:44:8 | x | 1 | +| 44 | split4 | test.py:44:8:44:16 | Compare | 1 | +| 44 | split4 | test.py:44:13:44:16 | None | 1 | +| 45 | split4 | test.py:45:9:45:9 | x | 1 | +| 45 | split4 | test.py:45:13:45:20 | not_none | 1 | +| 45 | split4 | test.py:45:13:45:22 | not_none() | 1 | +| 46 | split4 | test.py:46:5:46:5 | c | 1 | +| 46 | split4 | test.py:46:5:46:17 | IfExp | 1 | +| 46 | split4 | test.py:46:10:46:10 | b | 1 | +| 46 | split4 | test.py:46:17:46:17 | c | 1 | +| 47 | split4 | test.py:47:5:47:12 | Return | 1 | +| 47 | split4 | test.py:47:12:47:12 | x | 1 | +| 49 | split_carefully_5 | test.py:49:23:49:23 | x | 1 | +| 50 | split_carefully_5 | test.py:50:8:50:8 | x | 1 | +| 50 | split_carefully_5 | test.py:50:8:50:16 | Compare | 1 | +| 50 | split_carefully_5 | test.py:50:13:50:16 | None | 1 | +| 51 | split_carefully_5 | test.py:51:9:51:9 | x | 1 | +| 51 | split_carefully_5 | test.py:51:13:51:20 | not_none | 1 | +| 51 | split_carefully_5 | test.py:51:13:51:22 | not_none() | 1 | +| 52 | split_carefully_5 | test.py:52:8:52:8 | x | 1 | +| 53 | split_carefully_5 | test.py:53:9:53:12 | Pass | 1 | +| 54 | split_carefully_5 | test.py:54:5:54:12 | Return | 1 | +| 54 | split_carefully_5 | test.py:54:12:54:12 | x | 1 | +| 58 | dont_split_globals | test.py:58:8:58:11 | cond | 1 | +| 59 | dont_split_globals | test.py:59:9:59:12 | Pass | 1 | +| 60 | dont_split_globals | test.py:60:5:60:31 | call_could_alter_any_global | 2 | +| 60 | dont_split_globals | test.py:60:5:60:33 | call_could_alter_any_global() | 2 | +| 61 | dont_split_globals | test.py:61:8:61:11 | cond | 2 | +| 62 | dont_split_globals | test.py:62:9:62:12 | Pass | 2 | +| 64 | limit_splitting1 | test.py:64:22:64:22 | a | 1 | +| 64 | limit_splitting1 | test.py:64:24:64:24 | b | 1 | +| 64 | limit_splitting1 | test.py:64:26:64:26 | c | 1 | +| 64 | limit_splitting1 | test.py:64:28:64:28 | d | 1 | +| 65 | limit_splitting1 | test.py:65:8:65:8 | a | 1 | +| 65 | limit_splitting1 | test.py:65:8:65:16 | Compare | 1 | +| 65 | limit_splitting1 | test.py:65:13:65:16 | None | 1 | +| 65 | limit_splitting1 | test.py:65:19:65:19 | a | 1 | +| 65 | limit_splitting1 | test.py:65:23:65:25 | Str | 1 | +| 66 | limit_splitting1 | test.py:66:8:66:8 | b | 1 | +| 66 | limit_splitting1 | test.py:66:8:66:16 | Compare | 1 | +| 66 | limit_splitting1 | test.py:66:13:66:16 | None | 1 | +| 66 | limit_splitting1 | test.py:66:19:66:19 | b | 1 | +| 66 | limit_splitting1 | test.py:66:23:66:25 | Str | 1 | +| 67 | limit_splitting1 | test.py:67:8:67:8 | c | 1 | +| 67 | limit_splitting1 | test.py:67:8:67:16 | Compare | 1 | +| 67 | limit_splitting1 | test.py:67:13:67:16 | None | 1 | +| 67 | limit_splitting1 | test.py:67:19:67:19 | c | 1 | +| 67 | limit_splitting1 | test.py:67:23:67:25 | Str | 1 | +| 68 | limit_splitting1 | test.py:68:8:68:8 | d | 1 | +| 68 | limit_splitting1 | test.py:68:8:68:16 | Compare | 1 | +| 68 | limit_splitting1 | test.py:68:13:68:16 | None | 1 | +| 68 | limit_splitting1 | test.py:68:19:68:19 | d | 1 | +| 68 | limit_splitting1 | test.py:68:23:68:25 | Str | 1 | +| 69 | limit_splitting1 | test.py:69:5:69:8 | Pass | 1 | +| 77 | limit_splitting2 | test.py:77:22:77:22 | a | 1 | +| 77 | limit_splitting2 | test.py:77:24:77:24 | b | 1 | +| 77 | limit_splitting2 | test.py:77:26:77:26 | c | 1 | +| 77 | limit_splitting2 | test.py:77:28:77:28 | d | 1 | +| 78 | limit_splitting2 | test.py:78:8:78:8 | a | 1 | +| 79 | limit_splitting2 | test.py:79:9:79:12 | Pass | 1 | +| 80 | limit_splitting2 | test.py:80:8:80:8 | b | 2 | +| 81 | limit_splitting2 | test.py:81:9:81:12 | Pass | 2 | +| 82 | limit_splitting2 | test.py:82:8:82:8 | c | 4 | +| 83 | limit_splitting2 | test.py:83:9:83:12 | Pass | 4 | +| 84 | limit_splitting2 | test.py:84:8:84:8 | d | 4 | +| 85 | limit_splitting2 | test.py:85:9:85:12 | Pass | 4 | +| 87 | limit_splitting2 | test.py:87:8:87:8 | a | 4 | +| 88 | limit_splitting2 | test.py:88:9:88:10 | a1 | 2 | +| 89 | limit_splitting2 | test.py:89:8:89:8 | b | 4 | +| 90 | limit_splitting2 | test.py:90:9:90:10 | b1 | 2 | +| 92 | limit_splitting2 | test.py:92:8:92:8 | c | 4 | +| 93 | limit_splitting2 | test.py:93:9:93:10 | c1 | 4 | +| 94 | limit_splitting2 | test.py:94:8:94:8 | d | 4 | +| 95 | limit_splitting2 | test.py:95:9:95:10 | d1 | 4 | +| 98 | split_on_numbers | test.py:98:5:98:8 | Try | 1 | +| 99 | split_on_numbers | test.py:99:9:99:12 | call | 1 | +| 99 | split_on_numbers | test.py:99:9:99:14 | call() | 1 | +| 100 | split_on_numbers | test.py:100:9:100:9 | x | 1 | +| 100 | split_on_numbers | test.py:100:13:100:14 | UnaryExpr | 1 | +| 100 | split_on_numbers | test.py:100:14:100:14 | IntegerLiteral | 1 | +| 101 | split_on_numbers | test.py:101:5:101:11 | ExceptStmt | 1 | +| 102 | split_on_numbers | test.py:102:9:102:9 | x | 1 | +| 102 | split_on_numbers | test.py:102:13:102:13 | IntegerLiteral | 1 | +| 103 | split_on_numbers | test.py:103:8:103:8 | x | 2 | +| 104 | split_on_numbers | test.py:104:9:104:12 | Pass | 1 | +| 107 | split_try_except_else | test.py:107:5:107:8 | Try | 1 | +| 108 | split_try_except_else | test.py:108:9:108:12 | call | 1 | +| 108 | split_try_except_else | test.py:108:9:108:14 | call() | 1 | +| 109 | split_try_except_else | test.py:109:5:109:11 | ExceptStmt | 1 | +| 110 | split_try_except_else | test.py:110:9:110:9 | x | 1 | +| 110 | split_try_except_else | test.py:110:13:110:13 | IntegerLiteral | 1 | +| 112 | split_try_except_else | test.py:112:9:112:9 | x | 1 | +| 112 | split_try_except_else | test.py:112:13:112:13 | IntegerLiteral | 1 | +| 113 | split_try_except_else | test.py:113:8:113:8 | x | 2 | +| 114 | split_try_except_else | test.py:114:9:114:12 | Pass | 1 | +| 119 | logging | test.py:119:5:119:8 | Try | 1 | +| 120 | logging | test.py:120:16:120:22 | ImportExpr | 1 | +| 120 | logging | test.py:120:16:120:22 | module1 | 1 | +| 121 | logging | test.py:121:16:121:22 | ImportExpr | 1 | +| 121 | logging | test.py:121:16:121:22 | module2 | 1 | +| 123 | logging | test.py:123:5:123:23 | ExceptStmt | 1 | +| 123 | logging | test.py:123:12:123:22 | ImportError | 1 | +| 124 | logging | test.py:124:9:124:15 | module1 | 1 | +| 124 | logging | test.py:124:19:124:22 | None | 1 | +| 126 | logging | test.py:126:8:126:14 | module1 | 2 | +| 127 | logging | test.py:127:9:127:12 | inst | 1 | +| 127 | logging | test.py:127:16:127:22 | module2 | 1 | +| 127 | logging | test.py:127:16:127:28 | Attribute | 1 | +| 127 | logging | test.py:127:16:127:30 | Attribute() | 1 | +| 131 | split5 | test.py:131:5:131:8 | Try | 1 | +| 132 | split5 | test.py:132:9:132:12 | call | 1 | +| 132 | split5 | test.py:132:9:132:14 | call() | 1 | +| 133 | split5 | test.py:133:9:133:9 | x | 1 | +| 133 | split5 | test.py:133:13:133:16 | True | 1 | +| 134 | split5 | test.py:134:5:134:11 | ExceptStmt | 1 | +| 135 | split5 | test.py:135:9:135:9 | x | 1 | +| 135 | split5 | test.py:135:13:135:17 | False | 1 | +| 136 | split5 | test.py:136:8:136:12 | UnaryExpr | 2 | +| 136 | split5 | test.py:136:12:136:12 | x | 2 | +| 137 | split5 | test.py:137:9:137:12 | Pass | 1 | +| 140 | split6 | test.py:140:5:140:8 | Try | 1 | +| 141 | split6 | test.py:141:9:141:12 | call | 1 | +| 141 | split6 | test.py:141:9:141:14 | call() | 1 | +| 142 | split6 | test.py:142:9:142:9 | x | 1 | +| 142 | split6 | test.py:142:13:142:16 | True | 1 | +| 143 | split6 | test.py:143:5:143:11 | ExceptStmt | 1 | +| 144 | split6 | test.py:144:9:144:9 | x | 1 | +| 144 | split6 | test.py:144:13:144:17 | False | 1 | +| 145 | split6 | test.py:145:8:145:16 | UnaryExpr | 2 | +| 145 | split6 | test.py:145:12:145:16 | UnaryExpr | 2 | +| 145 | split6 | test.py:145:16:145:16 | x | 2 | +| 146 | split6 | test.py:146:9:146:12 | Pass | 1 | +| 149 | split7 | test.py:149:5:149:8 | Try | 1 | +| 150 | split7 | test.py:150:9:150:12 | call | 1 | +| 150 | split7 | test.py:150:9:150:14 | call() | 1 | +| 151 | split7 | test.py:151:9:151:9 | x | 1 | +| 151 | split7 | test.py:151:13:151:20 | UnaryExpr | 1 | +| 151 | split7 | test.py:151:17:151:20 | True | 1 | +| 152 | split7 | test.py:152:5:152:11 | ExceptStmt | 1 | +| 153 | split7 | test.py:153:9:153:9 | x | 1 | +| 153 | split7 | test.py:153:13:153:21 | UnaryExpr | 1 | +| 153 | split7 | test.py:153:17:153:21 | False | 1 | +| 154 | split7 | test.py:154:8:154:8 | x | 2 | +| 155 | split7 | test.py:155:9:155:12 | Pass | 1 | +| 157 | split8 | test.py:157:12:157:15 | cond | 1 | +| 158 | split8 | test.py:158:8:158:11 | cond | 1 | +| 159 | split8 | test.py:159:9:159:9 | t | 1 | +| 159 | split8 | test.py:159:13:159:16 | True | 1 | +| 161 | split8 | test.py:161:9:161:9 | t | 1 | +| 161 | split8 | test.py:161:13:161:17 | False | 1 | +| 162 | split8 | test.py:162:8:162:15 | UnaryExpr | 2 | +| 162 | split8 | test.py:162:12:162:15 | cond | 2 | +| 163 | split8 | test.py:163:12:163:12 | t | 1 | +| 164 | split8 | test.py:164:13:164:16 | Pass | 0 | +| 167 | split9 | test.py:167:12:167:14 | var | 1 | +| 168 | split9 | test.py:168:8:168:10 | var | 1 | +| 168 | split9 | test.py:168:8:168:18 | Compare | 1 | +| 168 | split9 | test.py:168:15:168:18 | None | 1 | +| 169 | split9 | test.py:169:9:169:10 | a1 | 1 | +| 171 | split9 | test.py:171:9:171:10 | a2 | 1 | +| 172 | split9 | test.py:172:8:172:10 | var | 2 | +| 172 | split9 | test.py:172:8:172:22 | Compare | 2 | +| 172 | split9 | test.py:172:19:172:22 | None | 2 | +| 173 | split9 | test.py:173:9:173:10 | b1 | 1 | +| 175 | split9 | test.py:175:9:175:10 | b2 | 1 | +| 177 | split10 | test.py:177:13:177:15 | var | 1 | +| 178 | split10 | test.py:178:8:178:10 | var | 1 | +| 179 | split10 | test.py:179:9:179:10 | a1 | 1 | +| 181 | split10 | test.py:181:9:181:10 | a2 | 1 | +| 182 | split10 | test.py:182:8:182:10 | var | 2 | +| 182 | split10 | test.py:182:8:182:22 | Compare | 2 | +| 182 | split10 | test.py:182:19:182:22 | None | 2 | +| 183 | split10 | test.py:183:9:183:10 | b1 | 2 | +| 185 | split10 | test.py:185:9:185:10 | b2 | 1 | +| 187 | split11 | test.py:187:13:187:15 | var | 1 | +| 188 | split11 | test.py:188:8:188:10 | var | 1 | +| 188 | split11 | test.py:188:8:188:18 | Compare | 1 | +| 188 | split11 | test.py:188:15:188:18 | None | 1 | +| 189 | split11 | test.py:189:9:189:10 | a1 | 1 | +| 191 | split11 | test.py:191:9:191:10 | a2 | 1 | +| 192 | split11 | test.py:192:8:192:10 | var | 2 | +| 193 | split11 | test.py:193:9:193:10 | b1 | 1 | +| 195 | split11 | test.py:195:9:195:10 | b2 | 2 | +| 197 | dont_split_on_unrelated_variables | test.py:197:39:197:42 | var1 | 1 | +| 197 | dont_split_on_unrelated_variables | test.py:197:45:197:48 | var2 | 1 | +| 198 | dont_split_on_unrelated_variables | test.py:198:8:198:11 | var1 | 1 | +| 198 | dont_split_on_unrelated_variables | test.py:198:8:198:19 | Compare | 1 | +| 198 | dont_split_on_unrelated_variables | test.py:198:16:198:19 | None | 1 | +| 199 | dont_split_on_unrelated_variables | test.py:199:9:199:10 | a1 | 1 | +| 201 | dont_split_on_unrelated_variables | test.py:201:9:201:10 | a2 | 1 | +| 202 | dont_split_on_unrelated_variables | test.py:202:8:202:11 | var2 | 1 | +| 202 | dont_split_on_unrelated_variables | test.py:202:8:202:23 | Compare | 1 | +| 202 | dont_split_on_unrelated_variables | test.py:202:20:202:23 | None | 1 | +| 203 | dont_split_on_unrelated_variables | test.py:203:9:203:10 | b1 | 1 | +| 205 | dont_split_on_unrelated_variables | test.py:205:9:205:10 | b2 | 1 | +| 208 | split12 | test.py:208:5:208:8 | Try | 1 | +| 209 | split12 | test.py:209:9:209:12 | call | 1 | +| 209 | split12 | test.py:209:9:209:14 | call() | 1 | +| 210 | split12 | test.py:210:9:210:9 | x | 1 | +| 210 | split12 | test.py:210:13:210:16 | None | 1 | +| 211 | split12 | test.py:211:5:211:11 | ExceptStmt | 1 | +| 212 | split12 | test.py:212:16:212:16 | ImportExpr | 1 | +| 212 | split12 | test.py:212:16:212:16 | x | 1 | +| 213 | split12 | test.py:213:8:213:8 | x | 2 | +| 214 | split12 | test.py:214:9:214:12 | Pass | 1 | +| 217 | split13 | test.py:217:5:217:7 | var | 1 | +| 217 | split13 | test.py:217:11:217:14 | cond | 1 | +| 217 | split13 | test.py:217:11:217:16 | cond() | 1 | +| 218 | split13 | test.py:218:8:218:10 | var | 1 | +| 219 | split13 | test.py:219:9:219:10 | a1 | 1 | +| 221 | split13 | test.py:221:9:221:10 | a2 | 1 | +| 222 | split13 | test.py:222:5:222:8 | Try | 2 | +| 223 | split13 | test.py:223:9:223:10 | b1 | 2 | +| 225 | split13 | test.py:225:12:225:14 | var | 4 | +| 226 | split13 | test.py:226:13:226:14 | a3 | 2 | +| 230 | split14 | test.py:230:5:230:8 | flag | 1 | +| 230 | split14 | test.py:230:12:230:16 | False | 1 | +| 231 | split14 | test.py:231:5:231:8 | Try | 1 | +| 232 | split14 | test.py:232:9:232:9 | x | 1 | +| 232 | split14 | test.py:232:13:232:21 | something | 1 | +| 232 | split14 | test.py:232:13:232:23 | something() | 1 | +| 233 | split14 | test.py:233:5:233:21 | ExceptStmt | 1 | +| 233 | split14 | test.py:233:12:233:20 | Exception | 1 | +| 234 | split14 | test.py:234:9:234:10 | IntegerLiteral | 1 | +| 235 | split14 | test.py:235:9:235:12 | flag | 1 | +| 235 | split14 | test.py:235:16:235:19 | True | 1 | +| 236 | split14 | test.py:236:8:236:15 | UnaryExpr | 2 | +| 236 | split14 | test.py:236:12:236:15 | flag | 2 | +| 238 | split14 | test.py:238:9:238:12 | Pass | 1 | +| 240 | split15 | test.py:240:13:240:15 | var | 1 | +| 241 | split15 | test.py:241:8:241:10 | var | 1 | +| 242 | split15 | test.py:242:9:242:13 | other | 1 | +| 242 | split15 | test.py:242:17:242:17 | IntegerLiteral | 1 | +| 243 | split15 | test.py:243:8:243:14 | UnaryExpr | 2 | +| 243 | split15 | test.py:243:8:243:28 | BoolExpr | 2 | +| 243 | split15 | test.py:243:12:243:14 | var | 2 | +| 243 | split15 | test.py:243:19:243:23 | other | 1 | +| 243 | split15 | test.py:243:19:243:28 | Attribute | 1 | +| 244 | split15 | test.py:244:9:244:12 | Pass | 2 | +| 247 | split16 | test.py:247:5:247:5 | x | 1 | +| 247 | split16 | test.py:247:9:247:12 | True | 1 | +| 248 | split16 | test.py:248:8:248:11 | cond | 1 | +| 249 | split16 | test.py:249:9:249:9 | x | 1 | +| 249 | split16 | test.py:249:13:249:16 | None | 1 | +| 250 | split16 | test.py:250:8:250:8 | x | 2 | +| 251 | split16 | test.py:251:9:251:11 | use | 1 | +| 251 | split16 | test.py:251:9:251:14 | use() | 1 | +| 251 | split16 | test.py:251:13:251:13 | x | 1 | +| 253 | dont_split_on_different_ssa | test.py:253:33:253:35 | var | 1 | +| 254 | dont_split_on_different_ssa | test.py:254:8:254:10 | var | 1 | +| 255 | dont_split_on_different_ssa | test.py:255:9:255:10 | a1 | 1 | +| 257 | dont_split_on_different_ssa | test.py:257:9:257:10 | a2 | 1 | +| 258 | dont_split_on_different_ssa | test.py:258:5:258:7 | var | 1 | +| 258 | dont_split_on_different_ssa | test.py:258:11:258:14 | func | 1 | +| 258 | dont_split_on_different_ssa | test.py:258:11:258:16 | func() | 1 | +| 259 | dont_split_on_different_ssa | test.py:259:8:259:10 | var | 1 | +| 259 | dont_split_on_different_ssa | test.py:259:8:259:22 | Compare | 1 | +| 259 | dont_split_on_different_ssa | test.py:259:19:259:22 | None | 1 | +| 260 | dont_split_on_different_ssa | test.py:260:9:260:10 | b1 | 1 | +| 262 | dont_split_on_different_ssa | test.py:262:9:262:10 | b2 | 1 | +| 264 | split17 | test.py:264:13:264:15 | var | 1 | +| 266 | split17 | test.py:266:8:266:10 | var | 1 | +| 267 | split17 | test.py:267:9:267:10 | a1 | 1 | +| 269 | split17 | test.py:269:9:269:10 | a2 | 1 | +| 270 | split17 | test.py:270:8:270:14 | UnaryExpr | 2 | +| 270 | split17 | test.py:270:12:270:14 | var | 2 | +| 271 | split17 | test.py:271:9:271:10 | b1 | 1 | +| 273 | split17 | test.py:273:9:273:10 | b2 | 1 | +| 274 | split17 | test.py:274:8:274:10 | var | 2 | +| 275 | split17 | test.py:275:9:275:10 | c1 | 1 | +| 277 | split17 | test.py:277:9:277:10 | c2 | 1 | +| 278 | split17 | test.py:278:8:278:10 | var | 2 | +| 279 | split17 | test.py:279:9:279:10 | d1 | 1 | +| 281 | split17 | test.py:281:9:281:10 | d2 | 1 | +| 282 | split17 | test.py:282:8:282:10 | var | 2 | +| 283 | split17 | test.py:283:9:283:10 | e1 | 1 | +| 285 | split17 | test.py:285:9:285:10 | e2 | 1 | +| 287 | split18 | test.py:287:13:287:15 | var | 1 | +| 289 | split18 | test.py:289:8:289:10 | var | 1 | +| 290 | split18 | test.py:290:9:290:10 | a1 | 1 | +| 292 | split18 | test.py:292:9:292:10 | a2 | 1 | +| 293 | split18 | test.py:293:8:293:10 | var | 2 | +| 293 | split18 | test.py:293:8:293:22 | Compare | 2 | +| 293 | split18 | test.py:293:19:293:22 | None | 2 | +| 294 | split18 | test.py:294:9:294:10 | b1 | 2 | +| 296 | split18 | test.py:296:9:296:10 | b2 | 1 | +| 297 | split18 | test.py:297:8:297:10 | var | 3 | +| 297 | split18 | test.py:297:8:297:18 | Compare | 3 | +| 297 | split18 | test.py:297:15:297:18 | None | 3 | +| 298 | split18 | test.py:298:9:298:10 | c1 | 1 | +| 300 | split18 | test.py:300:9:300:10 | c2 | 2 | +| 301 | split18 | test.py:301:8:301:10 | var | 3 | +| 302 | split18 | test.py:302:9:302:10 | d1 | 1 | +| 304 | split18 | test.py:304:9:304:10 | d2 | 2 | +| 305 | split18 | test.py:305:8:305:10 | var | 3 | +| 306 | split18 | test.py:306:9:306:10 | e1 | 1 | +| 308 | split18 | test.py:308:9:308:10 | e2 | 2 | +| 310 | split_on_boolean_only | test.py:310:27:310:27 | x | 1 | +| 311 | split_on_boolean_only | test.py:311:8:311:8 | x | 1 | +| 312 | split_on_boolean_only | test.py:312:9:312:10 | a1 | 1 | +| 314 | split_on_boolean_only | test.py:314:9:314:10 | a2 | 1 | +| 315 | split_on_boolean_only | test.py:315:8:315:8 | x | 2 | +| 315 | split_on_boolean_only | test.py:315:8:315:20 | Compare | 2 | +| 315 | split_on_boolean_only | test.py:315:17:315:20 | None | 2 | +| 316 | split_on_boolean_only | test.py:316:9:316:10 | b1 | 2 | +| 318 | split_on_boolean_only | test.py:318:9:318:10 | b2 | 1 | +| 319 | split_on_boolean_only | test.py:319:8:319:8 | x | 3 | +| 320 | split_on_boolean_only | test.py:320:9:320:10 | c1 | 1 | +| 322 | split_on_boolean_only | test.py:322:9:322:10 | c2 | 2 | +| 324 | split_on_none_aswell | test.py:324:26:324:26 | x | 1 | +| 325 | split_on_none_aswell | test.py:325:8:325:8 | x | 1 | +| 326 | split_on_none_aswell | test.py:326:9:326:10 | a1 | 1 | +| 328 | split_on_none_aswell | test.py:328:9:328:10 | a2 | 1 | +| 329 | split_on_none_aswell | test.py:329:8:329:8 | x | 2 | +| 329 | split_on_none_aswell | test.py:329:8:329:20 | Compare | 2 | +| 329 | split_on_none_aswell | test.py:329:17:329:20 | None | 2 | +| 330 | split_on_none_aswell | test.py:330:9:330:10 | b1 | 2 | +| 332 | split_on_none_aswell | test.py:332:9:332:10 | b2 | 1 | +| 333 | split_on_none_aswell | test.py:333:8:333:8 | x | 3 | +| 333 | split_on_none_aswell | test.py:333:8:333:16 | Compare | 3 | +| 333 | split_on_none_aswell | test.py:333:13:333:16 | None | 3 | +| 334 | split_on_none_aswell | test.py:334:9:334:10 | c1 | 1 | +| 336 | split_on_none_aswell | test.py:336:9:336:10 | c2 | 2 | +| 338 | split_on_or_defn | test.py:338:22:338:24 | var | 1 | +| 339 | split_on_or_defn | test.py:339:8:339:10 | var | 1 | +| 340 | split_on_or_defn | test.py:340:9:340:11 | obj | 1 | +| 340 | split_on_or_defn | test.py:340:15:340:19 | thing | 1 | +| 340 | split_on_or_defn | test.py:340:15:340:21 | thing() | 1 | +| 341 | split_on_or_defn | test.py:341:8:341:14 | UnaryExpr | 2 | +| 341 | split_on_or_defn | test.py:341:8:341:26 | BoolExpr | 2 | +| 341 | split_on_or_defn | test.py:341:12:341:14 | var | 2 | +| 341 | split_on_or_defn | test.py:341:19:341:21 | obj | 1 | +| 341 | split_on_or_defn | test.py:341:19:341:26 | Attribute | 1 | +| 342 | split_on_or_defn | test.py:342:9:342:9 | x | 2 | +| 345 | split_on_exception | test.py:345:5:345:8 | flag | 1 | +| 345 | split_on_exception | test.py:345:12:345:16 | False | 1 | +| 346 | split_on_exception | test.py:346:5:346:8 | Try | 1 | +| 347 | split_on_exception | test.py:347:9:347:9 | x | 1 | +| 347 | split_on_exception | test.py:347:13:347:24 | do_something | 1 | +| 347 | split_on_exception | test.py:347:13:347:26 | do_something() | 1 | +| 348 | split_on_exception | test.py:348:5:348:21 | ExceptStmt | 1 | +| 348 | split_on_exception | test.py:348:12:348:20 | Exception | 1 | +| 349 | split_on_exception | test.py:349:9:349:12 | flag | 1 | +| 349 | split_on_exception | test.py:349:16:349:19 | True | 1 | +| 350 | split_on_exception | test.py:350:8:350:15 | UnaryExpr | 2 | +| 350 | split_on_exception | test.py:350:12:350:15 | flag | 2 | +| 351 | split_on_exception | test.py:351:9:351:9 | x | 1 | +| 353 | partially_useful_split | test.py:353:28:353:31 | cond | 1 | +| 354 | partially_useful_split | test.py:354:8:354:11 | cond | 1 | +| 355 | partially_useful_split | test.py:355:9:355:9 | x | 1 | +| 355 | partially_useful_split | test.py:355:13:355:16 | None | 1 | +| 357 | partially_useful_split | test.py:357:9:357:9 | x | 1 | +| 357 | partially_useful_split | test.py:357:13:357:29 | something_or_none | 1 | +| 357 | partially_useful_split | test.py:357:13:357:31 | something_or_none() | 1 | +| 358 | partially_useful_split | test.py:358:5:358:15 | other_stuff | 2 | +| 358 | partially_useful_split | test.py:358:5:358:17 | other_stuff() | 2 | +| 359 | partially_useful_split | test.py:359:8:359:8 | x | 2 | +| 360 | partially_useful_split | test.py:360:9:360:10 | a1 | 1 | +| 362 | partially_useful_split | test.py:362:9:362:10 | a2 | 2 | +| 364 | dont_split_not_useful | test.py:364:27:364:30 | cond | 1 | +| 364 | dont_split_not_useful | test.py:364:33:364:33 | y | 1 | +| 365 | dont_split_not_useful | test.py:365:8:365:11 | cond | 1 | +| 366 | dont_split_not_useful | test.py:366:9:366:9 | x | 1 | +| 366 | dont_split_not_useful | test.py:366:13:366:16 | None | 1 | +| 368 | dont_split_not_useful | test.py:368:9:368:9 | x | 1 | +| 368 | dont_split_not_useful | test.py:368:13:368:29 | something_or_none | 1 | +| 368 | dont_split_not_useful | test.py:368:13:368:31 | something_or_none() | 1 | +| 369 | dont_split_not_useful | test.py:369:5:369:15 | other_stuff | 1 | +| 369 | dont_split_not_useful | test.py:369:5:369:17 | other_stuff() | 1 | +| 370 | dont_split_not_useful | test.py:370:8:370:8 | y | 1 | +| 371 | dont_split_not_useful | test.py:371:9:371:10 | a1 | 1 | +| 373 | dont_split_not_useful | test.py:373:9:373:10 | a2 | 1 | +| 376 | f | test.py:376:7:376:7 | x | 1 | +| 376 | f | test.py:376:9:376:9 | y | 1 | +| 377 | f | test.py:377:8:377:8 | x | 1 | +| 377 | f | test.py:377:8:377:14 | BoolExpr | 1 | +| 377 | f | test.py:377:14:377:14 | y | 1 | +| 378 | f | test.py:378:9:378:13 | Raise | 1 | +| 379 | f | test.py:379:8:379:19 | UnaryExpr | 3 | +| 379 | f | test.py:379:13:379:13 | x | 2 | +| 379 | f | test.py:379:13:379:18 | BoolExpr | 2 | +| 379 | f | test.py:379:18:379:18 | y | 1 | +| 380 | f | test.py:380:9:380:13 | Raise | 1 | +| 381 | f | test.py:381:5:381:8 | Pass | 2 | +| 383 | g | test.py:383:7:383:7 | x | 1 | +| 383 | g | test.py:383:9:383:9 | y | 1 | +| 384 | g | test.py:384:8:384:8 | x | 1 | +| 384 | g | test.py:384:8:384:14 | BoolExpr | 1 | +| 384 | g | test.py:384:14:384:14 | y | 1 | +| 385 | g | test.py:385:9:385:13 | Raise | 1 | +| 386 | g | test.py:386:8:386:8 | x | 2 | +| 386 | g | test.py:386:8:386:13 | BoolExpr | 2 | +| 386 | g | test.py:386:13:386:13 | y | 1 | +| 388 | g | test.py:388:9:388:12 | here | 2 | +| 389 | g | test.py:389:5:389:7 | end | 2 | +| 391 | h | test.py:391:7:391:7 | x | 1 | +| 391 | h | test.py:391:10:391:10 | y | 1 | +| 393 | h | test.py:393:9:396:18 | BoolExpr | 1 | +| 393 | h | test.py:393:10:393:10 | x | 1 | +| 393 | h | test.py:393:10:394:14 | BoolExpr | 1 | +| 394 | h | test.py:394:10:394:14 | UnaryExpr | 1 | +| 394 | h | test.py:394:14:394:14 | y | 1 | +| 395 | h | test.py:395:10:395:10 | x | 2 | +| 395 | h | test.py:395:10:396:17 | BoolExpr | 2 | +| 396 | h | test.py:396:10:396:10 | y | 1 | +| 396 | h | test.py:396:10:396:15 | Attribute | 1 | +| 396 | h | test.py:396:10:396:17 | Attribute() | 1 | +| 398 | h | test.py:398:9:398:12 | Pass | 1 | +| 400 | j | test.py:400:7:400:7 | a | 1 | +| 400 | j | test.py:400:10:400:10 | b | 1 | +| 401 | j | test.py:401:8:401:8 | a | 1 | +| 401 | j | test.py:401:8:401:13 | BoolExpr | 1 | +| 401 | j | test.py:401:13:401:13 | b | 1 | +| 402 | j | test.py:402:12:402:12 | a | 2 | +| 403 | j | test.py:403:13:403:16 | here | 1 | +| 405 | j | test.py:405:13:405:17 | there | 1 | +| 408 | split_on_strings | test.py:408:5:408:8 | Try | 1 | +| 409 | split_on_strings | test.py:409:9:409:18 | might_fail | 1 | +| 409 | split_on_strings | test.py:409:9:409:20 | might_fail() | 1 | +| 410 | split_on_strings | test.py:410:9:410:9 | x | 1 | +| 410 | split_on_strings | test.py:410:13:410:18 | Str | 1 | +| 411 | split_on_strings | test.py:411:5:411:11 | ExceptStmt | 1 | +| 412 | split_on_strings | test.py:412:9:412:9 | x | 1 | +| 412 | split_on_strings | test.py:412:13:412:16 | Str | 1 | +| 414 | split_on_strings | test.py:414:8:414:8 | x | 2 | +| 414 | split_on_strings | test.py:414:8:414:16 | Compare | 2 | +| 414 | split_on_strings | test.py:414:13:414:16 | Str | 2 | +| 415 | split_on_strings | test.py:415:9:415:12 | Pass | 2 | +| 416 | split_on_strings | test.py:416:5:416:8 | Pass | 2 | +| 419 | scipy_stylee | test.py:419:18:419:18 | x | 1 | +| 420 | scipy_stylee | test.py:420:5:420:31 | Assert | 2 | +| 420 | scipy_stylee | test.py:420:12:420:12 | x | 1 | +| 420 | scipy_stylee | test.py:420:12:420:31 | Compare | 1 | +| 420 | scipy_stylee | test.py:420:18:420:20 | Str | 1 | +| 420 | scipy_stylee | test.py:420:18:420:30 | Tuple | 1 | +| 420 | scipy_stylee | test.py:420:23:420:25 | Str | 1 | +| 420 | scipy_stylee | test.py:420:28:420:30 | Str | 1 | +| 421 | scipy_stylee | test.py:421:8:421:8 | x | 1 | +| 421 | scipy_stylee | test.py:421:8:421:15 | Compare | 1 | +| 421 | scipy_stylee | test.py:421:13:421:15 | Str | 1 | +| 422 | scipy_stylee | test.py:422:9:422:12 | Pass | 1 | +| 423 | scipy_stylee | test.py:423:10:423:10 | x | 1 | +| 423 | scipy_stylee | test.py:423:10:423:17 | Compare | 1 | +| 423 | scipy_stylee | test.py:423:15:423:17 | Str | 1 | +| 424 | scipy_stylee | test.py:424:9:424:12 | Pass | 1 | +| 425 | scipy_stylee | test.py:425:10:425:10 | x | 1 | +| 425 | scipy_stylee | test.py:425:10:425:17 | Compare | 1 | +| 425 | scipy_stylee | test.py:425:15:425:17 | Str | 1 | +| 426 | scipy_stylee | test.py:426:9:426:12 | Pass | 1 | +| 429 | scipy_stylee | test.py:429:9:429:12 | Pass | 1 | +| 431 | odasa_6674 | test.py:431:16:431:16 | x | 1 | +| 432 | odasa_6674 | test.py:432:5:432:9 | valid | 1 | +| 432 | odasa_6674 | test.py:432:13:432:16 | True | 1 | +| 433 | odasa_6674 | test.py:433:8:433:27 | dont_understand_this | 1 | +| 433 | odasa_6674 | test.py:433:8:433:29 | dont_understand_this() | 1 | +| 434 | odasa_6674 | test.py:434:9:434:12 | Try | 1 | +| 435 | odasa_6674 | test.py:435:13:435:21 | may_raise | 1 | +| 435 | odasa_6674 | test.py:435:13:435:23 | may_raise() | 1 | +| 436 | odasa_6674 | test.py:436:13:436:17 | score | 1 | +| 436 | odasa_6674 | test.py:436:21:436:21 | IntegerLiteral | 1 | +| 437 | odasa_6674 | test.py:437:9:437:24 | ExceptStmt | 1 | +| 437 | odasa_6674 | test.py:437:16:437:23 | KeyError | 1 | +| 438 | odasa_6674 | test.py:438:13:438:17 | valid | 1 | +| 438 | odasa_6674 | test.py:438:21:438:25 | False | 1 | +| 439 | odasa_6674 | test.py:439:12:439:20 | UnaryExpr | 2 | +| 439 | odasa_6674 | test.py:439:16:439:20 | valid | 2 | +| 440 | odasa_6674 | test.py:440:13:440:30 | Raise | 1 | +| 440 | odasa_6674 | test.py:440:19:440:28 | ValueError | 1 | +| 440 | odasa_6674 | test.py:440:19:440:30 | ValueError() | 1 | +| 442 | odasa_6674 | test.py:442:9:442:13 | score | 1 | +| 442 | odasa_6674 | test.py:442:17:442:17 | IntegerLiteral | 1 | +| 443 | odasa_6674 | test.py:443:5:443:16 | Return | 1 | +| 443 | odasa_6674 | test.py:443:12:443:16 | score | 1 | +| 445 | odasa_6625 | test.py:445:16:445:16 | k | 1 | +| 446 | odasa_6625 | test.py:446:5:446:9 | value | 1 | +| 446 | odasa_6625 | test.py:446:13:446:16 | Str | 1 | +| 447 | odasa_6625 | test.py:447:8:447:8 | k | 1 | +| 447 | odasa_6625 | test.py:447:8:447:17 | Attribute | 1 | +| 447 | odasa_6625 | test.py:447:8:447:25 | Attribute() | 1 | +| 447 | odasa_6625 | test.py:447:8:447:47 | BoolExpr | 1 | +| 447 | odasa_6625 | test.py:447:19:447:24 | Str | 1 | +| 447 | odasa_6625 | test.py:447:30:447:30 | k | 1 | +| 447 | odasa_6625 | test.py:447:30:447:39 | Attribute | 1 | +| 447 | odasa_6625 | test.py:447:30:447:47 | Attribute() | 1 | +| 447 | odasa_6625 | test.py:447:41:447:46 | Str | 1 | +| 448 | odasa_6625 | test.py:448:9:448:13 | value | 1 | +| 448 | odasa_6625 | test.py:448:17:448:17 | IntegerLiteral | 1 | +| 449 | odasa_6625 | test.py:449:8:449:8 | k | 1 | +| 449 | odasa_6625 | test.py:449:8:449:18 | Compare | 1 | +| 449 | odasa_6625 | test.py:449:13:449:18 | Str | 1 | +| 450 | odasa_6625 | test.py:450:9:450:31 | Return | 1 | +| 450 | odasa_6625 | test.py:450:16:450:20 | value | 1 | +| 450 | odasa_6625 | test.py:450:16:450:31 | BinaryExpr | 1 | +| 450 | odasa_6625 | test.py:450:24:450:31 | Str | 1 | +| 453 | avoid_redundant_split | test.py:453:27:453:27 | a | 1 | +| 454 | avoid_redundant_split | test.py:454:8:454:8 | a | 1 | +| 455 | avoid_redundant_split | test.py:455:9:455:9 | x | 1 | +| 455 | avoid_redundant_split | test.py:455:13:455:25 | unknown_thing | 1 | +| 455 | avoid_redundant_split | test.py:455:13:455:27 | unknown_thing() | 1 | +| 457 | avoid_redundant_split | test.py:457:9:457:9 | x | 1 | +| 457 | avoid_redundant_split | test.py:457:13:457:16 | None | 1 | +| 458 | avoid_redundant_split | test.py:458:8:458:8 | x | 2 | +| 459 | avoid_redundant_split | test.py:459:9:459:12 | Pass | 1 | +| 460 | avoid_redundant_split | test.py:460:8:460:8 | x | 2 | +| 461 | avoid_redundant_split | test.py:461:9:461:12 | Pass | 1 | +| 462 | avoid_redundant_split | test.py:462:5:462:15 | other_stuff | 2 | +| 462 | avoid_redundant_split | test.py:462:5:462:17 | other_stuff() | 2 | +| 463 | avoid_redundant_split | test.py:463:5:463:8 | Try | 2 | +| 464 | avoid_redundant_split | test.py:464:16:464:18 | ImportExpr | 2 | +| 464 | avoid_redundant_split | test.py:464:16:464:18 | foo | 2 | +| 465 | avoid_redundant_split | test.py:465:9:465:11 | var | 2 | +| 465 | avoid_redundant_split | test.py:465:15:465:18 | True | 2 | +| 466 | avoid_redundant_split | test.py:466:5:466:11 | ExceptStmt | 2 | +| 467 | avoid_redundant_split | test.py:467:9:467:11 | var | 2 | +| 467 | avoid_redundant_split | test.py:467:15:467:19 | False | 2 | +| 468 | avoid_redundant_split | test.py:468:8:468:10 | var | 4 | +| 469 | avoid_redundant_split | test.py:469:9:469:11 | foo | 2 | +| 469 | avoid_redundant_split | test.py:469:9:469:15 | Attribute | 2 | +| 469 | avoid_redundant_split | test.py:469:9:469:17 | Attribute() | 2 | diff --git a/python/ql/test/library-tests/ControlFlow/splitting/NodeCount.ql b/python/ql/test/library-tests/ControlFlow/splitting/NodeCount.ql new file mode 100644 index 000000000000..d9d5efbb4941 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/splitting/NodeCount.ql @@ -0,0 +1,8 @@ +import python + +from AstNode a, Scope s +where not a instanceof Import and not a instanceof If and not a instanceof AssignStmt and not a instanceof ExprStmt and +a.getScope() = s and +s instanceof Function +select +a.getLocation().getStartLine(), s.getName(), a, count(a.getAFlowNode()) diff --git a/python/ql/test/library-tests/ControlFlow/splitting/SuccessorCount.expected b/python/ql/test/library-tests/ControlFlow/splitting/SuccessorCount.expected new file mode 100644 index 000000000000..c65e6d1b64b0 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/splitting/SuccessorCount.expected @@ -0,0 +1,181 @@ +| 3 | split1 | test.py:3:8:3:11 | ControlFlowNode for cond | 2 | +| 5 | split1 | test.py:5:8:5:11 | ControlFlowNode for cond | 1 | +| 5 | split1 | test.py:5:8:5:11 | ControlFlowNode for cond | 1 | +| 9 | dont_split1 | test.py:9:8:9:11 | ControlFlowNode for cond | 2 | +| 12 | dont_split1 | test.py:12:8:12:11 | ControlFlowNode for cond | 2 | +| 16 | dont_split2 | test.py:16:8:16:11 | ControlFlowNode for cond | 2 | +| 19 | dont_split2 | test.py:19:8:19:11 | ControlFlowNode for cond | 2 | +| 29 | split2 | test.py:29:8:29:8 | ControlFlowNode for x | 1 | +| 29 | split2 | test.py:29:8:29:8 | ControlFlowNode for x | 1 | +| 38 | unclear_split3 | test.py:38:8:38:11 | ControlFlowNode for cond | 2 | +| 40 | unclear_split3 | test.py:40:8:40:8 | ControlFlowNode for x | 1 | +| 40 | unclear_split3 | test.py:40:8:40:8 | ControlFlowNode for x | 2 | +| 44 | split4 | test.py:44:8:44:16 | ControlFlowNode for Compare | 2 | +| 46 | split4 | test.py:46:10:46:10 | ControlFlowNode for b | 2 | +| 50 | split_carefully_5 | test.py:50:8:50:16 | ControlFlowNode for Compare | 2 | +| 52 | split_carefully_5 | test.py:52:8:52:8 | ControlFlowNode for x | 2 | +| 58 | dont_split_globals | test.py:58:8:58:11 | ControlFlowNode for cond | 2 | +| 61 | dont_split_globals | test.py:61:8:61:11 | ControlFlowNode for cond | 2 | +| 61 | dont_split_globals | test.py:61:8:61:11 | ControlFlowNode for cond | 2 | +| 65 | limit_splitting1 | test.py:65:8:65:16 | ControlFlowNode for Compare | 2 | +| 66 | limit_splitting1 | test.py:66:8:66:16 | ControlFlowNode for Compare | 2 | +| 67 | limit_splitting1 | test.py:67:8:67:16 | ControlFlowNode for Compare | 2 | +| 68 | limit_splitting1 | test.py:68:8:68:16 | ControlFlowNode for Compare | 2 | +| 78 | limit_splitting2 | test.py:78:8:78:8 | ControlFlowNode for a | 2 | +| 80 | limit_splitting2 | test.py:80:8:80:8 | ControlFlowNode for b | 2 | +| 80 | limit_splitting2 | test.py:80:8:80:8 | ControlFlowNode for b | 2 | +| 82 | limit_splitting2 | test.py:82:8:82:8 | ControlFlowNode for c | 2 | +| 82 | limit_splitting2 | test.py:82:8:82:8 | ControlFlowNode for c | 2 | +| 82 | limit_splitting2 | test.py:82:8:82:8 | ControlFlowNode for c | 2 | +| 82 | limit_splitting2 | test.py:82:8:82:8 | ControlFlowNode for c | 2 | +| 84 | limit_splitting2 | test.py:84:8:84:8 | ControlFlowNode for d | 2 | +| 84 | limit_splitting2 | test.py:84:8:84:8 | ControlFlowNode for d | 2 | +| 84 | limit_splitting2 | test.py:84:8:84:8 | ControlFlowNode for d | 2 | +| 84 | limit_splitting2 | test.py:84:8:84:8 | ControlFlowNode for d | 2 | +| 87 | limit_splitting2 | test.py:87:8:87:8 | ControlFlowNode for a | 1 | +| 87 | limit_splitting2 | test.py:87:8:87:8 | ControlFlowNode for a | 1 | +| 87 | limit_splitting2 | test.py:87:8:87:8 | ControlFlowNode for a | 1 | +| 87 | limit_splitting2 | test.py:87:8:87:8 | ControlFlowNode for a | 1 | +| 89 | limit_splitting2 | test.py:89:8:89:8 | ControlFlowNode for b | 1 | +| 89 | limit_splitting2 | test.py:89:8:89:8 | ControlFlowNode for b | 1 | +| 89 | limit_splitting2 | test.py:89:8:89:8 | ControlFlowNode for b | 1 | +| 89 | limit_splitting2 | test.py:89:8:89:8 | ControlFlowNode for b | 1 | +| 92 | limit_splitting2 | test.py:92:8:92:8 | ControlFlowNode for c | 2 | +| 92 | limit_splitting2 | test.py:92:8:92:8 | ControlFlowNode for c | 2 | +| 92 | limit_splitting2 | test.py:92:8:92:8 | ControlFlowNode for c | 2 | +| 92 | limit_splitting2 | test.py:92:8:92:8 | ControlFlowNode for c | 2 | +| 94 | limit_splitting2 | test.py:94:8:94:8 | ControlFlowNode for d | 2 | +| 94 | limit_splitting2 | test.py:94:8:94:8 | ControlFlowNode for d | 2 | +| 94 | limit_splitting2 | test.py:94:8:94:8 | ControlFlowNode for d | 2 | +| 94 | limit_splitting2 | test.py:94:8:94:8 | ControlFlowNode for d | 2 | +| 103 | split_on_numbers | test.py:103:8:103:8 | ControlFlowNode for x | 1 | +| 103 | split_on_numbers | test.py:103:8:103:8 | ControlFlowNode for x | 1 | +| 113 | split_try_except_else | test.py:113:8:113:8 | ControlFlowNode for x | 1 | +| 113 | split_try_except_else | test.py:113:8:113:8 | ControlFlowNode for x | 1 | +| 126 | logging | test.py:126:8:126:14 | ControlFlowNode for module1 | 1 | +| 126 | logging | test.py:126:8:126:14 | ControlFlowNode for module1 | 1 | +| 136 | split5 | test.py:136:8:136:12 | ControlFlowNode for UnaryExpr | 1 | +| 136 | split5 | test.py:136:8:136:12 | ControlFlowNode for UnaryExpr | 1 | +| 145 | split6 | test.py:145:8:145:16 | ControlFlowNode for UnaryExpr | 1 | +| 145 | split6 | test.py:145:8:145:16 | ControlFlowNode for UnaryExpr | 1 | +| 154 | split7 | test.py:154:8:154:8 | ControlFlowNode for x | 1 | +| 154 | split7 | test.py:154:8:154:8 | ControlFlowNode for x | 1 | +| 158 | split8 | test.py:158:8:158:11 | ControlFlowNode for cond | 2 | +| 162 | split8 | test.py:162:8:162:15 | ControlFlowNode for UnaryExpr | 1 | +| 162 | split8 | test.py:162:8:162:15 | ControlFlowNode for UnaryExpr | 1 | +| 163 | split8 | test.py:163:12:163:12 | ControlFlowNode for t | 1 | +| 168 | split9 | test.py:168:8:168:18 | ControlFlowNode for Compare | 2 | +| 172 | split9 | test.py:172:8:172:22 | ControlFlowNode for Compare | 1 | +| 172 | split9 | test.py:172:8:172:22 | ControlFlowNode for Compare | 1 | +| 178 | split10 | test.py:178:8:178:10 | ControlFlowNode for var | 2 | +| 182 | split10 | test.py:182:8:182:22 | ControlFlowNode for Compare | 1 | +| 182 | split10 | test.py:182:8:182:22 | ControlFlowNode for Compare | 2 | +| 188 | split11 | test.py:188:8:188:18 | ControlFlowNode for Compare | 2 | +| 192 | split11 | test.py:192:8:192:10 | ControlFlowNode for var | 1 | +| 192 | split11 | test.py:192:8:192:10 | ControlFlowNode for var | 2 | +| 198 | dont_split_on_unrelated_variables | test.py:198:8:198:19 | ControlFlowNode for Compare | 2 | +| 202 | dont_split_on_unrelated_variables | test.py:202:8:202:23 | ControlFlowNode for Compare | 2 | +| 213 | split12 | test.py:213:8:213:8 | ControlFlowNode for x | 1 | +| 213 | split12 | test.py:213:8:213:8 | ControlFlowNode for x | 1 | +| 218 | split13 | test.py:218:8:218:10 | ControlFlowNode for var | 2 | +| 225 | split13 | test.py:225:12:225:14 | ControlFlowNode for var | 1 | +| 225 | split13 | test.py:225:12:225:14 | ControlFlowNode for var | 1 | +| 225 | split13 | test.py:225:12:225:14 | ControlFlowNode for var | 1 | +| 236 | split14 | test.py:236:8:236:15 | ControlFlowNode for UnaryExpr | 1 | +| 236 | split14 | test.py:236:8:236:15 | ControlFlowNode for UnaryExpr | 1 | +| 241 | split15 | test.py:241:8:241:10 | ControlFlowNode for var | 2 | +| 243 | split15 | test.py:243:8:243:14 | ControlFlowNode for UnaryExpr | 1 | +| 243 | split15 | test.py:243:8:243:14 | ControlFlowNode for UnaryExpr | 1 | +| 243 | split15 | test.py:243:19:243:28 | ControlFlowNode for Attribute | 2 | +| 248 | split16 | test.py:248:8:248:11 | ControlFlowNode for cond | 2 | +| 250 | split16 | test.py:250:8:250:8 | ControlFlowNode for x | 1 | +| 250 | split16 | test.py:250:8:250:8 | ControlFlowNode for x | 1 | +| 254 | dont_split_on_different_ssa | test.py:254:8:254:10 | ControlFlowNode for var | 2 | +| 259 | dont_split_on_different_ssa | test.py:259:8:259:22 | ControlFlowNode for Compare | 2 | +| 266 | split17 | test.py:266:8:266:10 | ControlFlowNode for var | 2 | +| 270 | split17 | test.py:270:8:270:14 | ControlFlowNode for UnaryExpr | 1 | +| 270 | split17 | test.py:270:8:270:14 | ControlFlowNode for UnaryExpr | 1 | +| 274 | split17 | test.py:274:8:274:10 | ControlFlowNode for var | 1 | +| 274 | split17 | test.py:274:8:274:10 | ControlFlowNode for var | 1 | +| 278 | split17 | test.py:278:8:278:10 | ControlFlowNode for var | 1 | +| 278 | split17 | test.py:278:8:278:10 | ControlFlowNode for var | 1 | +| 282 | split17 | test.py:282:8:282:10 | ControlFlowNode for var | 1 | +| 282 | split17 | test.py:282:8:282:10 | ControlFlowNode for var | 1 | +| 289 | split18 | test.py:289:8:289:10 | ControlFlowNode for var | 2 | +| 293 | split18 | test.py:293:8:293:22 | ControlFlowNode for Compare | 1 | +| 293 | split18 | test.py:293:8:293:22 | ControlFlowNode for Compare | 2 | +| 297 | split18 | test.py:297:8:297:18 | ControlFlowNode for Compare | 1 | +| 297 | split18 | test.py:297:8:297:18 | ControlFlowNode for Compare | 1 | +| 297 | split18 | test.py:297:8:297:18 | ControlFlowNode for Compare | 1 | +| 301 | split18 | test.py:301:8:301:10 | ControlFlowNode for var | 1 | +| 301 | split18 | test.py:301:8:301:10 | ControlFlowNode for var | 1 | +| 301 | split18 | test.py:301:8:301:10 | ControlFlowNode for var | 1 | +| 305 | split18 | test.py:305:8:305:10 | ControlFlowNode for var | 1 | +| 305 | split18 | test.py:305:8:305:10 | ControlFlowNode for var | 1 | +| 305 | split18 | test.py:305:8:305:10 | ControlFlowNode for var | 1 | +| 311 | split_on_boolean_only | test.py:311:8:311:8 | ControlFlowNode for x | 2 | +| 315 | split_on_boolean_only | test.py:315:8:315:20 | ControlFlowNode for Compare | 1 | +| 315 | split_on_boolean_only | test.py:315:8:315:20 | ControlFlowNode for Compare | 2 | +| 319 | split_on_boolean_only | test.py:319:8:319:8 | ControlFlowNode for x | 1 | +| 319 | split_on_boolean_only | test.py:319:8:319:8 | ControlFlowNode for x | 1 | +| 319 | split_on_boolean_only | test.py:319:8:319:8 | ControlFlowNode for x | 1 | +| 325 | split_on_none_aswell | test.py:325:8:325:8 | ControlFlowNode for x | 2 | +| 329 | split_on_none_aswell | test.py:329:8:329:20 | ControlFlowNode for Compare | 1 | +| 329 | split_on_none_aswell | test.py:329:8:329:20 | ControlFlowNode for Compare | 2 | +| 333 | split_on_none_aswell | test.py:333:8:333:16 | ControlFlowNode for Compare | 1 | +| 333 | split_on_none_aswell | test.py:333:8:333:16 | ControlFlowNode for Compare | 1 | +| 333 | split_on_none_aswell | test.py:333:8:333:16 | ControlFlowNode for Compare | 1 | +| 339 | split_on_or_defn | test.py:339:8:339:10 | ControlFlowNode for var | 2 | +| 341 | split_on_or_defn | test.py:341:8:341:14 | ControlFlowNode for UnaryExpr | 1 | +| 341 | split_on_or_defn | test.py:341:8:341:14 | ControlFlowNode for UnaryExpr | 1 | +| 341 | split_on_or_defn | test.py:341:19:341:26 | ControlFlowNode for Attribute | 2 | +| 350 | split_on_exception | test.py:350:8:350:15 | ControlFlowNode for UnaryExpr | 1 | +| 350 | split_on_exception | test.py:350:8:350:15 | ControlFlowNode for UnaryExpr | 1 | +| 354 | partially_useful_split | test.py:354:8:354:11 | ControlFlowNode for cond | 2 | +| 359 | partially_useful_split | test.py:359:8:359:8 | ControlFlowNode for x | 1 | +| 359 | partially_useful_split | test.py:359:8:359:8 | ControlFlowNode for x | 2 | +| 365 | dont_split_not_useful | test.py:365:8:365:11 | ControlFlowNode for cond | 2 | +| 370 | dont_split_not_useful | test.py:370:8:370:8 | ControlFlowNode for y | 2 | +| 377 | f | test.py:377:8:377:8 | ControlFlowNode for x | 2 | +| 377 | f | test.py:377:14:377:14 | ControlFlowNode for y | 2 | +| 379 | f | test.py:379:8:379:19 | ControlFlowNode for UnaryExpr | 1 | +| 379 | f | test.py:379:8:379:19 | ControlFlowNode for UnaryExpr | 1 | +| 379 | f | test.py:379:8:379:19 | ControlFlowNode for UnaryExpr | 1 | +| 379 | f | test.py:379:13:379:13 | ControlFlowNode for x | 1 | +| 379 | f | test.py:379:13:379:13 | ControlFlowNode for x | 1 | +| 379 | f | test.py:379:18:379:18 | ControlFlowNode for y | 2 | +| 384 | g | test.py:384:8:384:8 | ControlFlowNode for x | 2 | +| 384 | g | test.py:384:14:384:14 | ControlFlowNode for y | 2 | +| 386 | g | test.py:386:8:386:8 | ControlFlowNode for x | 1 | +| 386 | g | test.py:386:8:386:8 | ControlFlowNode for x | 1 | +| 386 | g | test.py:386:13:386:13 | ControlFlowNode for y | 2 | +| 393 | h | test.py:393:10:393:10 | ControlFlowNode for x | 2 | +| 394 | h | test.py:394:10:394:14 | ControlFlowNode for UnaryExpr | 2 | +| 395 | h | test.py:395:10:395:10 | ControlFlowNode for x | 1 | +| 395 | h | test.py:395:10:395:10 | ControlFlowNode for x | 1 | +| 396 | h | test.py:396:10:396:17 | ControlFlowNode for Attribute() | 2 | +| 401 | j | test.py:401:8:401:8 | ControlFlowNode for a | 2 | +| 401 | j | test.py:401:13:401:13 | ControlFlowNode for b | 2 | +| 402 | j | test.py:402:12:402:12 | ControlFlowNode for a | 1 | +| 402 | j | test.py:402:12:402:12 | ControlFlowNode for a | 1 | +| 414 | split_on_strings | test.py:414:8:414:16 | ControlFlowNode for Compare | 2 | +| 414 | split_on_strings | test.py:414:8:414:16 | ControlFlowNode for Compare | 2 | +| 420 | scipy_stylee | test.py:420:12:420:31 | ControlFlowNode for Compare | 2 | +| 421 | scipy_stylee | test.py:421:8:421:15 | ControlFlowNode for Compare | 2 | +| 423 | scipy_stylee | test.py:423:10:423:17 | ControlFlowNode for Compare | 2 | +| 425 | scipy_stylee | test.py:425:10:425:17 | ControlFlowNode for Compare | 2 | +| 433 | odasa_6674 | test.py:433:8:433:29 | ControlFlowNode for dont_understand_this() | 2 | +| 439 | odasa_6674 | test.py:439:12:439:20 | ControlFlowNode for UnaryExpr | 1 | +| 439 | odasa_6674 | test.py:439:12:439:20 | ControlFlowNode for UnaryExpr | 1 | +| 447 | odasa_6625 | test.py:447:8:447:25 | ControlFlowNode for Attribute() | 2 | +| 447 | odasa_6625 | test.py:447:30:447:47 | ControlFlowNode for Attribute() | 2 | +| 449 | odasa_6625 | test.py:449:8:449:18 | ControlFlowNode for Compare | 2 | +| 454 | avoid_redundant_split | test.py:454:8:454:8 | ControlFlowNode for a | 2 | +| 458 | avoid_redundant_split | test.py:458:8:458:8 | ControlFlowNode for x | 1 | +| 458 | avoid_redundant_split | test.py:458:8:458:8 | ControlFlowNode for x | 2 | +| 460 | avoid_redundant_split | test.py:460:8:460:8 | ControlFlowNode for x | 1 | +| 460 | avoid_redundant_split | test.py:460:8:460:8 | ControlFlowNode for x | 2 | +| 468 | avoid_redundant_split | test.py:468:8:468:10 | ControlFlowNode for var | 1 | +| 468 | avoid_redundant_split | test.py:468:8:468:10 | ControlFlowNode for var | 1 | +| 468 | avoid_redundant_split | test.py:468:8:468:10 | ControlFlowNode for var | 1 | +| 468 | avoid_redundant_split | test.py:468:8:468:10 | ControlFlowNode for var | 1 | diff --git a/python/ql/test/library-tests/ControlFlow/splitting/SuccessorCount.ql b/python/ql/test/library-tests/ControlFlow/splitting/SuccessorCount.ql new file mode 100644 index 000000000000..d865d9061c37 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/splitting/SuccessorCount.ql @@ -0,0 +1,9 @@ +import python + +from ControlFlowNode p, Scope s +where +p.getScope() = s and +(exists (p.getATrueSuccessor()) or exists(p.getAFalseSuccessor())) and +s instanceof Function +select +p.getLocation().getStartLine(), s.getName(), p, strictcount(p.getASuccessor()) diff --git a/python/ql/test/library-tests/ControlFlow/splitting/test.py b/python/ql/test/library-tests/ControlFlow/splitting/test.py new file mode 100644 index 000000000000..f01f83f4c5c5 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/splitting/test.py @@ -0,0 +1,471 @@ + +def split1(cond): + if cond: + pass + if cond: + pass + +def dont_split1(cond): + if cond: + pass + cond = f() + if cond: + pass + +def dont_split2(cond): + if cond: + pass + for cond in seq: pass + if cond: + pass + + +def split2(): + try: + call() + x = True + except: + x = False + if x: + pass + +def unclear_split3(): + try: # May be arguably better to split here. + call() + x = True + except: + x = False + if cond: # Currently split here + x = False + if x: + pass + +def split4(x): + if x is None: + x = not_none() + c if b else c + return x + +def split_carefully_5(x): + if x is None: + x = not_none() + if x: + pass + return x + + +def dont_split_globals(): + if cond: + pass + call_could_alter_any_global() + if cond: + pass + +def limit_splitting1(a,b,c,d): + if a is None: a = "a" + if b is None: b = "b" + if c is None: c = "c" + if d is None: d = "d" + pass + + + + + + + +def limit_splitting2(a,b,c,d): + if a: + pass + if b: + pass + if c: + pass + if d: + pass + #These should be pruned + if a: + a1 + if b: + b1 + #But not these + if c: + c1 + if d: + d1 + +def split_on_numbers(): + try: + call() + x = -1 + except: + x = 0 + if x: + pass + +def split_try_except_else(): + try: + call() + except: + x = 0 + else: + x = 1 + if x: + pass + +#Example taken from logging module +#Splitting should allow us to deduce that module2 is defined at point of use +def logging(): + try: + import module1 + import module2 + + except ImportError: + module1 = None + + if module1: + inst = module2.Class() + +#Handle 'not' as well. +def split5(): + try: + call() + x = True + except: + x = False + if not x: + pass + +def split6(): + try: + call() + x = True + except: + x = False + if not not x: + pass + +def split7(): + try: + call() + x = not True + except: + x = not False + if x: + pass + +def split8(cond): + if cond: + t = True + else: + t = False + if not cond: + if t: + pass + + +def split9(var): + if var is None: + a1 + else: + a2 + if var is not None: + b1 + else: + b2 + +def split10(var): + if var: + a1 + else: + a2 + if var is not None: + b1 + else: + b2 + +def split11(var): + if var is None: + a1 + else: + a2 + if var: + b1 + else: + b2 + +def dont_split_on_unrelated_variables(var1, var2): + if var1 is None: + a1 + else: + a2 + if var2 is not None: + b1 + else: + b2 + +def split12(): + try: + call() + x = None + except: + import x + if x: + pass + +def split13(): + var = cond() + if var: + a1 + else: + a2 + try: + b1 + finally: + if var: + a3 + + +def split14(): + flag = False + try: + x = something() + except Exception: + 99 + flag = True + if not flag: + #Should be split here + pass + +def split15(var): + if var: + other = 0 + if not var or other.attr: #other looks like it might be undefined, but it is defined. + pass + +def split16(): + x = True + if cond: + x = None + if x: + use(x) + +def dont_split_on_different_ssa(var): + if var: + a1 + else: + a2 + var = func() + if var is not None: + b1 + else: + b2 + +def split17(var): + #Should only be split once + if var: + a1 + else: + a2 + if not var: + b1 + else: + b2 + if var: + c1 + else: + c2 + if var: + d1 + else: + d2 + if var: + e1 + else: + e2 + +def split18(var): + #Should only be split once + if var: + a1 + else: + a2 + if var is not None: #Distinguishes between False and None + b1 + else: + b2 + if var is None: + c1 + else: + c2 + if var: + d1 + else: + d2 + if var: + e1 + else: + e2 + +def split_on_boolean_only(x): + if x: + a1 + else: + a2 + if x is not None: + b1 + else: + b2 + if x: + c1 + else: + c2 + +def split_on_none_aswell(x): + if x: + a1 + else: + a2 + if x is not None: + b1 + else: + b2 + if x is None: + c1 + else: + c2 + +def split_on_or_defn(var): + if var: + obj = thing() + if not var or obj.attr: # obj is defined if reached + x + +def split_on_exception(): + flag = False + try: + x = do_something() + except Exception: + flag = True + if not flag: + x # x is defined here + +def partially_useful_split(cond): + if cond: + x = None + else: + x = something_or_none() + other_stuff() + if x: + a1 + else: + a2 + +def dont_split_not_useful(cond, y): + if cond: + x = None + else: + x = something_or_none() + other_stuff() + if y: + a1 + else: + a2 + +#Splittings with boolean expressions: +def f(x,y): + if x and y: + raise + if not (x or y): + raise + pass + +def g(x,y): + if x and y: + raise + if x or y: + # Either x or y is true here (exclusive). + here + end + +def h(x, y): + if ( + (x and + not y) or + (x and + y.meth()) + ): + pass + +def j(a, b): + if a or b: + if a: + here + else: + there + +def split_on_strings(): + try: + might_fail() + x = "yes+" + except: + x = "no" + #We want to split here, even though we can't (easily) prune + if x == "no": + pass + pass + + +def scipy_stylee(x): + assert x in ("a", "b", "c") + if x == "a": + pass + elif x == "b": + pass + elif x == "c": + pass + else: + #unreachable + pass + +def odasa_6674(x): + valid = True + if dont_understand_this(): + try: + may_raise() + score = 0 + except KeyError: + valid = False + if not valid: + raise ValueError() + else: + score = 1 + return score + +def odasa_6625(k): + value = "hi" + if k.endswith('_min') or k.endswith('_max'): + value = 0 + if k == 'tags': + return value + " there" + + +def avoid_redundant_split(a): + if a: # Should split here + x = unknown_thing() + else: + x = None + if x: # but not here, + pass + if x: # or here, because + pass + other_stuff() + try: # we want to split here + import foo + var = True + except: + var = False + if var: + foo.bar() + + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/defns/test.expected b/python/ql/test/library-tests/ControlFlow/ssa/defns/test.expected new file mode 100644 index 000000000000..1860e4f280fa --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/defns/test.expected @@ -0,0 +1,31 @@ +| test1.py | SSA Variable l0 | 6 | test1.py:6:5:6:6 | SSA Variable l0 | 6 | +| test1.py | SSA Variable l1 | 7 | test1.py:7:5:7:6 | SSA Variable l1 | 7 | +| test1.py | SSA Variable l2 | 8 | test1.py:8:5:8:6 | SSA Variable l2 | 8 | +| test1.py | SSA Variable no_phi | 5 | test1.py:5:5:5:10 | SSA Variable no_phi | 5 | +| test2.py | SSA Variable cond | 5 | test2.py:5:5:5:8 | SSA Variable cond | 5 | +| test2.py | SSA Variable l0 | 7 | test2.py:7:9:7:10 | SSA Variable l0 | 7 | +| test2.py | SSA Variable l0 | 9 | test2.py:9:9:9:10 | SSA Variable l0 | 9 | +| test2.py | SSA Variable l0 | 10 | test2.py:7:9:7:10 | SSA Variable l0 | 7 | +| test2.py | SSA Variable l0 | 10 | test2.py:9:9:9:10 | SSA Variable l0 | 9 | +| test2.py | SSA Variable with_phi | 4 | test2.py:4:5:4:12 | SSA Variable with_phi | 4 | +| test3.py | SSA Variable l0 | 9 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | +| test3.py | SSA Variable l0 | 9 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | +| test3.py | SSA Variable l0 | 11 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | +| test3.py | SSA Variable l0 | 11 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | +| test3.py | SSA Variable l0 | 13 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | +| test3.py | SSA Variable l0 | 13 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | +| test3.py | SSA Variable phi_in_try | 4 | test3.py:4:5:4:14 | SSA Variable phi_in_try | 4 | +| test4.py | SSA Variable del1 | 2 | test4.py:2:5:2:8 | SSA Variable del1 | 2 | +| test4.py | SSA Variable del2 | 8 | test4.py:8:5:8:8 | SSA Variable del2 | 8 | +| test4.py | SSA Variable x | 3 | test4.py:3:5:3:5 | SSA Variable x | 3 | +| test4.py | SSA Variable x | 5 | test4.py:5:5:5:5 | SSA Variable x | 5 | +| test4.py | SSA Variable x | 9 | test4.py:9:5:9:5 | SSA Variable x | 9 | +| test4.py | SSA Variable x | 11 | test4.py:11:13:11:13 | SSA Variable x | 11 | +| test4.py | SSA Variable x | 13 | test4.py:13:9:13:9 | SSA Variable x | 13 | +| test4.py | SSA Variable x | 14 | test4.py:11:13:11:13 | SSA Variable x | 11 | +| test4.py | SSA Variable x | 14 | test4.py:13:9:13:9 | SSA Variable x | 13 | +| test5.py | SSA Variable a | 0 | test5.py:4:9:4:9 | SSA Variable a | 4 | +| test5.py | SSA Variable a | 0 | test5.py:9:9:9:9 | SSA Variable a | 9 | +| test5.py | SSA Variable a | 4 | test5.py:4:9:4:9 | SSA Variable a | 4 | +| test5.py | SSA Variable a | 9 | test5.py:9:9:9:9 | SSA Variable a | 9 | +| test6.py | SSA Variable a | 4 | test6.py:4:9:4:9 | SSA Variable a | 4 | diff --git a/python/ql/test/library-tests/ControlFlow/ssa/defns/test.ql b/python/ql/test/library-tests/ControlFlow/ssa/defns/test.ql new file mode 100644 index 000000000000..c8ce28554558 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/defns/test.ql @@ -0,0 +1,6 @@ +import python + +from SsaVariable var, SsaVariable def +where def = var.getAnUltimateDefinition() +select var.getLocation().getFile().getShortName(), +var.toString(), var.getLocation().getStartLine(), def, def.getLocation().getStartLine() diff --git a/python/ql/test/library-tests/ControlFlow/ssa/defns/test1.py b/python/ql/test/library-tests/ControlFlow/ssa/defns/test1.py new file mode 100644 index 000000000000..d6fe26089b05 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/defns/test1.py @@ -0,0 +1,10 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + + +def no_phi(cond): + l0 = 0 + l1 = 1 + l2 = l0 + l1 + return l2 + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/defns/test2.py b/python/ql/test/library-tests/ControlFlow/ssa/defns/test2.py new file mode 100644 index 000000000000..d02c4c370bb2 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/defns/test2.py @@ -0,0 +1,11 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + +def with_phi( + cond): + if cond: + l0 = 0 + else: + l0 = 1 + return l0 + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/defns/test3.py b/python/ql/test/library-tests/ControlFlow/ssa/defns/test3.py new file mode 100644 index 000000000000..ce2d9ac920e6 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/defns/test3.py @@ -0,0 +1,14 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + +def phi_in_try(): + try: + try: + a_call() + finally: + l0 = 0 + another_call() + except: + pass + return l0 + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/defns/test4.py b/python/ql/test/library-tests/ControlFlow/ssa/defns/test4.py new file mode 100644 index 000000000000..1f4426514a26 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/defns/test4.py @@ -0,0 +1,15 @@ + +def del1(): + x = 0 + del x + x = 0 + return x + +def del2(): + x = 0 + if random(): + del x + else: + x = 1 + return x + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/defns/test5.py b/python/ql/test/library-tests/ControlFlow/ssa/defns/test5.py new file mode 100644 index 000000000000..8f900254ec67 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/defns/test5.py @@ -0,0 +1,11 @@ + +if x: + + def a(): + pass + +else: + + def a(): + pass + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/defns/test6.py b/python/ql/test/library-tests/ControlFlow/ssa/defns/test6.py new file mode 100644 index 000000000000..d148a5c745bf --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/defns/test6.py @@ -0,0 +1,6 @@ + +if x: + + def a(): + pass + del a diff --git a/python/ql/test/library-tests/ControlFlow/ssa/deletions/test.expected b/python/ql/test/library-tests/ControlFlow/ssa/deletions/test.expected new file mode 100644 index 000000000000..a80d1fe8beb7 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/deletions/test.expected @@ -0,0 +1,6 @@ +| 5 | ControlFlowNode for cond | cond | other | +| 7 | ControlFlowNode for u2 | u2 | delete | +| 10 | ControlFlowNode for u3 | u3 | delete | +| 10 | ControlFlowNode for use | use | other | +| 18 | ControlFlowNode for u2 | u2 | delete | +| 21 | ControlFlowNode for u3 | u3 | delete | \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/ssa/deletions/test.py b/python/ql/test/library-tests/ControlFlow/ssa/deletions/test.py new file mode 100644 index 000000000000..16f192326430 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/deletions/test.py @@ -0,0 +1,21 @@ +from mod import cond + +del u1 + +if cond: + u2 = 0 +del u2 + +del u3 +use(u3) + + +def f(): + del u1 + + if cond: + u2 = 0 + del u2 + + del u3 + use(u3) \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/ssa/deletions/test.ql b/python/ql/test/library-tests/ControlFlow/ssa/deletions/test.ql new file mode 100644 index 000000000000..b220553d07b6 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/deletions/test.ql @@ -0,0 +1,14 @@ +import python + + +from SsaVariable v, string kind, ControlFlowNode use, int line +where use = v.getAUse() and +( + kind = "delete" and v.getDefinition().isDelete() + or + kind = "other " and not v.getDefinition().isDelete() +) +and line = use.getLocation().getStartLine() +and line != 0 + +select line, use.toString(), v.getId(), kind diff --git a/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/phi_input_test.expected b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/phi_input_test.expected new file mode 100644 index 000000000000..ffdf9771e918 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/phi_input_test.expected @@ -0,0 +1,12 @@ +| test2.py | SSA Variable l0 | 10 | test2.py:7:9:7:10 | SSA Variable l0 | 7 | 7 | +| test2.py | SSA Variable l0 | 10 | test2.py:9:9:9:10 | SSA Variable l0 | 9 | 9 | +| test2.py | SSA Variable l1 | 19 | test2.py:14:5:14:6 | SSA Variable l1 | 14 | 16 | +| test2.py | SSA Variable l1 | 19 | test2.py:18:9:18:10 | SSA Variable l1 | 18 | 18 | +| test3.py | SSA Variable l0 | 11 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | 10 | +| test3.py | SSA Variable l0 | 11 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | 10 | +| test3.py | SSA Variable l0 | 13 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | 10 | +| test3.py | SSA Variable l0 | 13 | test3.py:11:5:11:11 | SSA Variable l0 | 11 | 12 | +| test4.py | SSA Variable x | 14 | test4.py:11:13:11:13 | SSA Variable x | 11 | 11 | +| test4.py | SSA Variable x | 14 | test4.py:13:9:13:9 | SSA Variable x | 13 | 13 | +| test5.py | SSA Variable a | 0 | test5.py:4:9:4:9 | SSA Variable a | 4 | 4 | +| test5.py | SSA Variable a | 0 | test5.py:9:9:9:9 | SSA Variable a | 9 | 9 | diff --git a/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/phi_input_test.ql b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/phi_input_test.ql new file mode 100644 index 000000000000..5cfb210da249 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/phi_input_test.ql @@ -0,0 +1,7 @@ +import python + +from SsaVariable var, SsaVariable arg, BasicBlock pred +where pred = var.getPredecessorBlockForPhiArgument(arg) +select var.getLocation().getFile().getShortName(), +var.toString(), var.getLocation().getStartLine(), arg, arg.getLocation().getStartLine(), pred.getLastNode().getLocation().getStartLine() + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test.expected b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test.expected new file mode 100644 index 000000000000..53dd04825335 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test.expected @@ -0,0 +1,12 @@ +| test2.py | SSA Variable l0 | 10 | test2.py:7:9:7:10 | SSA Variable l0 | 7 | +| test2.py | SSA Variable l0 | 10 | test2.py:9:9:9:10 | SSA Variable l0 | 9 | +| test2.py | SSA Variable l1 | 19 | test2.py:14:5:14:6 | SSA Variable l1 | 14 | +| test2.py | SSA Variable l1 | 19 | test2.py:18:9:18:10 | SSA Variable l1 | 18 | +| test3.py | SSA Variable l0 | 11 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | +| test3.py | SSA Variable l0 | 11 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | +| test3.py | SSA Variable l0 | 13 | test3.py:9:13:9:14 | SSA Variable l0 | 9 | +| test3.py | SSA Variable l0 | 13 | test3.py:11:5:11:11 | SSA Variable l0 | 11 | +| test4.py | SSA Variable x | 14 | test4.py:11:13:11:13 | SSA Variable x | 11 | +| test4.py | SSA Variable x | 14 | test4.py:13:9:13:9 | SSA Variable x | 13 | +| test5.py | SSA Variable a | 0 | test5.py:4:9:4:9 | SSA Variable a | 4 | +| test5.py | SSA Variable a | 0 | test5.py:9:9:9:9 | SSA Variable a | 9 | diff --git a/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test.ql b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test.ql new file mode 100644 index 000000000000..6c4f617e1728 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test.ql @@ -0,0 +1,7 @@ +import python + +from SsaVariable var, SsaVariable arg +where arg = var.getAPhiInput() +select var.getLocation().getFile().getShortName(), +var.toString(), var.getLocation().getStartLine(), arg, arg.getLocation().getStartLine() + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test1.py b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test1.py new file mode 100644 index 000000000000..d6fe26089b05 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test1.py @@ -0,0 +1,10 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + + +def no_phi(cond): + l0 = 0 + l1 = 1 + l2 = l0 + l1 + return l2 + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test2.py b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test2.py new file mode 100644 index 000000000000..de38a986ceea --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test2.py @@ -0,0 +1,19 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + +def with_phi( + cond): + if cond: + l0 = 0 + else: + l0 = 1 + return l0 + +def with_phi2( + cond): + l1 = 0 + if cond: + pass + else: + l1 = 1 + return l1 diff --git a/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test3.py b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test3.py new file mode 100644 index 000000000000..ce2d9ac920e6 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test3.py @@ -0,0 +1,14 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + +def phi_in_try(): + try: + try: + a_call() + finally: + l0 = 0 + another_call() + except: + pass + return l0 + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test4.py b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test4.py new file mode 100644 index 000000000000..1f4426514a26 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test4.py @@ -0,0 +1,15 @@ + +def del1(): + x = 0 + del x + x = 0 + return x + +def del2(): + x = 0 + if random(): + del x + else: + x = 1 + return x + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test5.py b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test5.py new file mode 100644 index 000000000000..8f900254ec67 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test5.py @@ -0,0 +1,11 @@ + +if x: + + def a(): + pass + +else: + + def a(): + pass + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test6.py b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test6.py new file mode 100644 index 000000000000..d148a5c745bf --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/phi-nodes/test6.py @@ -0,0 +1,6 @@ + +if x: + + def a(): + pass + del a diff --git a/python/ql/test/library-tests/ControlFlow/ssa/undefined/test.expected b/python/ql/test/library-tests/ControlFlow/ssa/undefined/test.expected new file mode 100644 index 000000000000..8ca7c0a9309c --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/undefined/test.expected @@ -0,0 +1,2 @@ +| 15 | SSA Variable module2 | +| 20 | SSA Variable x | diff --git a/python/ql/test/library-tests/ControlFlow/ssa/undefined/test.py b/python/ql/test/library-tests/ControlFlow/ssa/undefined/test.py new file mode 100644 index 000000000000..fb7420506dc3 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/undefined/test.py @@ -0,0 +1,20 @@ +from outside import cond + +try: + import module1 +except ImportError: + quit() + +module1 + +try: + import module2 +except ImportError: + print("Error") + +module2 + +if cond(): + x = 0 + +x diff --git a/python/ql/test/library-tests/ControlFlow/ssa/undefined/test.ql b/python/ql/test/library-tests/ControlFlow/ssa/undefined/test.ql new file mode 100644 index 000000000000..df5df70d8275 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/undefined/test.ql @@ -0,0 +1,7 @@ + +import python + +from SsaVariable var +where var.maybeUndefined() +select +var.getDefinition().getLocation().getStartLine(), var.toString() diff --git a/python/ql/test/library-tests/ControlFlow/ssa/uses/test.expected b/python/ql/test/library-tests/ControlFlow/ssa/uses/test.expected new file mode 100644 index 000000000000..6d7ad357cba7 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/uses/test.expected @@ -0,0 +1,11 @@ +| test1.py | ControlFlowNode for l0 | 8 | SSA Variable l0 | 6 | +| test1.py | ControlFlowNode for l1 | 8 | SSA Variable l1 | 7 | +| test1.py | ControlFlowNode for l2 | 9 | SSA Variable l2 | 8 | +| test1.py | Exit node for Module test1 | 0 | SSA Variable no_phi | 5 | +| test2.py | ControlFlowNode for cond | 6 | SSA Variable cond | 5 | +| test2.py | ControlFlowNode for l0 | 10 | SSA Variable l0 | 10 | +| test2.py | Exit node for Module test2 | 0 | SSA Variable with_phi | 4 | +| test3.py | ControlFlowNode for l0 | 13 | SSA Variable l0 | 13 | +| test3.py | Exit node for Module test3 | 0 | SSA Variable phi_in_try | 4 | +| test5.py | Exit node for Module test5 | 0 | SSA Variable a | 0 | +| test6.py | ControlFlowNode for a | 6 | SSA Variable a | 4 | diff --git a/python/ql/test/library-tests/ControlFlow/ssa/uses/test.ql b/python/ql/test/library-tests/ControlFlow/ssa/uses/test.ql new file mode 100644 index 000000000000..9a3f4e924528 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/uses/test.ql @@ -0,0 +1,6 @@ +import python + +from ControlFlowNode use, SsaVariable def +where def.getAUse() = use +select use.getLocation().getFile().getShortName(), +use.toString(), use.getLocation().getStartLine(), def.toString(), def.getLocation().getStartLine() diff --git a/python/ql/test/library-tests/ControlFlow/ssa/uses/test1.py b/python/ql/test/library-tests/ControlFlow/ssa/uses/test1.py new file mode 100644 index 000000000000..d6fe26089b05 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/uses/test1.py @@ -0,0 +1,10 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + + +def no_phi(cond): + l0 = 0 + l1 = 1 + l2 = l0 + l1 + return l2 + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/uses/test2.py b/python/ql/test/library-tests/ControlFlow/ssa/uses/test2.py new file mode 100644 index 000000000000..d02c4c370bb2 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/uses/test2.py @@ -0,0 +1,11 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + +def with_phi( + cond): + if cond: + l0 = 0 + else: + l0 = 1 + return l0 + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/uses/test3.py b/python/ql/test/library-tests/ControlFlow/ssa/uses/test3.py new file mode 100644 index 000000000000..ce2d9ac920e6 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/uses/test3.py @@ -0,0 +1,14 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + +def phi_in_try(): + try: + try: + a_call() + finally: + l0 = 0 + another_call() + except: + pass + return l0 + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/uses/test5.py b/python/ql/test/library-tests/ControlFlow/ssa/uses/test5.py new file mode 100644 index 000000000000..8f900254ec67 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/uses/test5.py @@ -0,0 +1,11 @@ + +if x: + + def a(): + pass + +else: + + def a(): + pass + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/uses/test6.py b/python/ql/test/library-tests/ControlFlow/ssa/uses/test6.py new file mode 100644 index 000000000000..d148a5c745bf --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/uses/test6.py @@ -0,0 +1,6 @@ + +if x: + + def a(): + pass + del a diff --git a/python/ql/test/library-tests/ControlFlow/ssa/vars/test.expected b/python/ql/test/library-tests/ControlFlow/ssa/vars/test.expected new file mode 100644 index 000000000000..be7f90aea765 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/vars/test.expected @@ -0,0 +1,26 @@ +| test1.py | test1.py:5:5:5:10 | SSA Variable no_phi | 5 | +| test1.py | test1.py:6:5:6:6 | SSA Variable l0 | 6 | +| test1.py | test1.py:7:5:7:6 | SSA Variable l1 | 7 | +| test1.py | test1.py:8:5:8:6 | SSA Variable l2 | 8 | +| test2.py | test2.py:4:5:4:12 | SSA Variable with_phi | 4 | +| test2.py | test2.py:5:5:5:8 | SSA Variable cond | 5 | +| test2.py | test2.py:7:9:7:10 | SSA Variable l0 | 7 | +| test2.py | test2.py:9:9:9:10 | SSA Variable l0 | 9 | +| test2.py | test2.py:10:12:10:13 | SSA Variable l0 | 10 | +| test3.py | test3.py:4:5:4:14 | SSA Variable phi_in_try | 4 | +| test3.py | test3.py:9:13:9:14 | SSA Variable l0 | 9 | +| test3.py | test3.py:9:13:9:14 | SSA Variable l0 | 9 | +| test3.py | test3.py:11:5:11:11 | SSA Variable l0 | 11 | +| test3.py | test3.py:13:12:13:13 | SSA Variable l0 | 13 | +| test4.py | test4.py:2:5:2:8 | SSA Variable del1 | 2 | +| test4.py | test4.py:3:5:3:5 | SSA Variable x | 3 | +| test4.py | test4.py:5:5:5:5 | SSA Variable x | 5 | +| test4.py | test4.py:8:5:8:8 | SSA Variable del2 | 8 | +| test4.py | test4.py:9:5:9:5 | SSA Variable x | 9 | +| test4.py | test4.py:11:13:11:13 | SSA Variable x | 11 | +| test4.py | test4.py:13:9:13:9 | SSA Variable x | 13 | +| test4.py | test4.py:14:12:14:12 | SSA Variable x | 14 | +| test5.py | test5.py:0:0:0:0 | SSA Variable a | 0 | +| test5.py | test5.py:4:9:4:9 | SSA Variable a | 4 | +| test5.py | test5.py:9:9:9:9 | SSA Variable a | 9 | +| test6.py | test6.py:4:9:4:9 | SSA Variable a | 4 | diff --git a/python/ql/test/library-tests/ControlFlow/ssa/vars/test.ql b/python/ql/test/library-tests/ControlFlow/ssa/vars/test.ql new file mode 100644 index 000000000000..5e2dd530ad9e --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/vars/test.ql @@ -0,0 +1,7 @@ +import python + +from SsaVariable var + +select var.getLocation().getFile().getShortName(), +var, var.getLocation().getStartLine() + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/vars/test1.py b/python/ql/test/library-tests/ControlFlow/ssa/vars/test1.py new file mode 100644 index 000000000000..d6fe26089b05 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/vars/test1.py @@ -0,0 +1,10 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + + +def no_phi(cond): + l0 = 0 + l1 = 1 + l2 = l0 + l1 + return l2 + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/vars/test2.py b/python/ql/test/library-tests/ControlFlow/ssa/vars/test2.py new file mode 100644 index 000000000000..d02c4c370bb2 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/vars/test2.py @@ -0,0 +1,11 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + +def with_phi( + cond): + if cond: + l0 = 0 + else: + l0 = 1 + return l0 + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/vars/test3.py b/python/ql/test/library-tests/ControlFlow/ssa/vars/test3.py new file mode 100644 index 000000000000..ce2d9ac920e6 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/vars/test3.py @@ -0,0 +1,14 @@ +#Weird formatting is so that all uses and defn are on separate lines +#to assist checking test results. + +def phi_in_try(): + try: + try: + a_call() + finally: + l0 = 0 + another_call() + except: + pass + return l0 + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/vars/test4.py b/python/ql/test/library-tests/ControlFlow/ssa/vars/test4.py new file mode 100644 index 000000000000..1f4426514a26 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/vars/test4.py @@ -0,0 +1,15 @@ + +def del1(): + x = 0 + del x + x = 0 + return x + +def del2(): + x = 0 + if random(): + del x + else: + x = 1 + return x + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/vars/test5.py b/python/ql/test/library-tests/ControlFlow/ssa/vars/test5.py new file mode 100644 index 000000000000..8f900254ec67 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/vars/test5.py @@ -0,0 +1,11 @@ + +if x: + + def a(): + pass + +else: + + def a(): + pass + diff --git a/python/ql/test/library-tests/ControlFlow/ssa/vars/test6.py b/python/ql/test/library-tests/ControlFlow/ssa/vars/test6.py new file mode 100644 index 000000000000..d148a5c745bf --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/ssa/vars/test6.py @@ -0,0 +1,6 @@ + +if x: + + def a(): + pass + del a diff --git a/python/ql/test/library-tests/ControlFlow/truefalse/Branch.expected b/python/ql/test/library-tests/ControlFlow/truefalse/Branch.expected new file mode 100644 index 000000000000..a5feb4fc4e88 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/truefalse/Branch.expected @@ -0,0 +1,14 @@ +| 2 | boolops.py:2:9:6:25 | ControlFlowNode for UnaryExpr | +| 2 | boolops.py:2:9:6:25 | ControlFlowNode for UnaryExpr | +| 3 | boolops.py:3:13:3:13 | ControlFlowNode for x | +| 4 | boolops.py:4:17:6:24 | ControlFlowNode for UnaryExpr | +| 4 | boolops.py:4:17:6:24 | ControlFlowNode for UnaryExpr | +| 5 | boolops.py:5:22:5:23 | ControlFlowNode for y1 | +| 6 | boolops.py:6:22:6:23 | ControlFlowNode for z1 | +| 7 | boolops.py:7:8:11:23 | ControlFlowNode for UnaryExpr | +| 7 | boolops.py:7:8:11:23 | ControlFlowNode for UnaryExpr | +| 8 | boolops.py:8:12:8:12 | ControlFlowNode for x | +| 9 | boolops.py:9:15:11:22 | ControlFlowNode for UnaryExpr | +| 9 | boolops.py:9:15:11:22 | ControlFlowNode for UnaryExpr | +| 10 | boolops.py:10:20:10:21 | ControlFlowNode for y2 | +| 11 | boolops.py:11:20:11:21 | ControlFlowNode for z2 | diff --git a/python/ql/test/library-tests/ControlFlow/truefalse/Branch.ql b/python/ql/test/library-tests/ControlFlow/truefalse/Branch.ql new file mode 100644 index 000000000000..76b235c80064 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/truefalse/Branch.ql @@ -0,0 +1,12 @@ +/** + * @name Branch + * @description Insert description here... + * @kind problem + * @problem.severity warning + */ + +import python + +from ControlFlowNode f +where f.isBranch() and f.getLocation().getFile().getShortName() = "boolops.py" +select f.getLocation().getStartLine(), f diff --git a/python/ql/test/library-tests/ControlFlow/truefalse/ExceptionalSuccessors.expected b/python/ql/test/library-tests/ControlFlow/truefalse/ExceptionalSuccessors.expected new file mode 100644 index 000000000000..b84e4f43f920 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/truefalse/ExceptionalSuccessors.expected @@ -0,0 +1,23 @@ +| true_false_test.py | 14 | true_false_test.py:14:12:14:16 | ControlFlowNode for cond4 | ControlFlowNode for Pass | +| true_false_test.py | 15 | true_false_test.py:15:13:15:17 | ControlFlowNode for true4 | ControlFlowNode for Pass | +| true_false_test.py | 15 | true_false_test.py:15:13:15:19 | ControlFlowNode for true4() | ControlFlowNode for Pass | +| true_false_test.py | 17 | true_false_test.py:17:13:17:18 | ControlFlowNode for false4 | ControlFlowNode for Pass | +| true_false_test.py | 17 | true_false_test.py:17:13:17:20 | ControlFlowNode for false4() | ControlFlowNode for Pass | +| true_false_test.py | 19 | true_false_test.py:19:9:19:12 | ControlFlowNode for Pass | Entry node for Function func | +| true_false_test.py | 22 | true_false_test.py:22:13:22:17 | ControlFlowNode for true5 | ControlFlowNode for ExceptStmt | +| true_false_test.py | 22 | true_false_test.py:22:13:22:19 | ControlFlowNode for true5() | ControlFlowNode for ExceptStmt | +| true_false_test.py | 35 | true_false_test.py:35:18:35:26 | ControlFlowNode for range() | Entry node for Function func | +| true_false_test.py | 48 | true_false_test.py:48:17:48:24 | ControlFlowNode for true12() | ControlFlowNode for ExceptStmt | +| true_false_test.py | 48 | true_false_test.py:48:17:48:24 | ControlFlowNode for true12() | Entry node for Function func | +| true_succ.py | 8 | true_succ.py:8:21:8:39 | ControlFlowNode for open() | ControlFlowNode for ExceptStmt | +| true_succ.py | 8 | true_succ.py:8:21:8:39 | ControlFlowNode for open() | ControlFlowNode for f | +| true_succ.py | 9 | true_succ.py:9:17:9:23 | ControlFlowNode for Attribute | ControlFlowNode for ExceptStmt | +| true_succ.py | 9 | true_succ.py:9:17:9:23 | ControlFlowNode for Attribute | ControlFlowNode for f | +| true_succ.py | 9 | true_succ.py:9:17:9:32 | ControlFlowNode for Attribute() | ControlFlowNode for ExceptStmt | +| true_succ.py | 9 | true_succ.py:9:17:9:32 | ControlFlowNode for Attribute() | ControlFlowNode for f | +| true_succ.py | 11 | true_succ.py:11:17:11:19 | ControlFlowNode for sys | ControlFlowNode for f | +| true_succ.py | 11 | true_succ.py:11:17:11:24 | ControlFlowNode for Attribute | ControlFlowNode for f | +| true_succ.py | 11 | true_succ.py:11:17:11:27 | ControlFlowNode for Attribute() | ControlFlowNode for f | +| true_succ.py | 13 | true_succ.py:13:16:13:28 | ControlFlowNode for Compare | Entry node for Function example | +| true_succ.py | 13 | true_succ.py:13:31:13:39 | ControlFlowNode for Attribute() | Entry node for Function example | +| true_succ.py | 13 | true_succ.py:13:31:13:39 | ControlFlowNode for Attribute() | Entry node for Function example | diff --git a/python/ql/test/library-tests/ControlFlow/truefalse/ExceptionalSuccessors.ql b/python/ql/test/library-tests/ControlFlow/truefalse/ExceptionalSuccessors.ql new file mode 100644 index 000000000000..f7bf00db01b3 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/truefalse/ExceptionalSuccessors.ql @@ -0,0 +1,16 @@ +/** + * @name TrueFalseSuccessors Test + * @description Tests true/false successors + * @kind problem + * @problem.severity warning + */ + +import python + +from ControlFlowNode p, ControlFlowNode s +where +s = p.getAnExceptionalSuccessor() +or +// Add fake edges for node that raise out of scope +p.isExceptionalExit(_) and s = p.getScope().getEntryNode() +select p.getLocation().getFile().getShortName(), p.getLocation().getStartLine(), p, s.toString() diff --git a/python/ql/test/library-tests/ControlFlow/truefalse/TrueAndFalseSuccessor.expected b/python/ql/test/library-tests/ControlFlow/truefalse/TrueAndFalseSuccessor.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/ControlFlow/truefalse/TrueAndFalseSuccessor.ql b/python/ql/test/library-tests/ControlFlow/truefalse/TrueAndFalseSuccessor.ql new file mode 100644 index 000000000000..d5d8323a3a26 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/truefalse/TrueAndFalseSuccessor.ql @@ -0,0 +1,7 @@ + + +import python + +from ControlFlowNode f +where f.getATrueSuccessor() = f.getAFalseSuccessor() +select f.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/ControlFlow/truefalse/TrueFalseSuccessors.expected b/python/ql/test/library-tests/ControlFlow/truefalse/TrueFalseSuccessors.expected new file mode 100644 index 000000000000..4590ee6f390b --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/truefalse/TrueFalseSuccessors.expected @@ -0,0 +1,82 @@ +| boolops.py | 2 | boolops.py:2:9:6:25 | ControlFlowNode for UnaryExpr | ControlFlowNode for p | False | +| boolops.py | 2 | boolops.py:2:9:6:25 | ControlFlowNode for UnaryExpr | ControlFlowNode for p | True | +| boolops.py | 3 | boolops.py:3:13:3:13 | ControlFlowNode for x | ControlFlowNode for BoolExpr | True | +| boolops.py | 3 | boolops.py:3:13:3:13 | ControlFlowNode for x | ControlFlowNode for UnaryExpr | False | +| boolops.py | 4 | boolops.py:4:17:6:24 | ControlFlowNode for UnaryExpr | ControlFlowNode for UnaryExpr | False | +| boolops.py | 4 | boolops.py:4:17:6:24 | ControlFlowNode for UnaryExpr | ControlFlowNode for UnaryExpr | True | +| boolops.py | 5 | boolops.py:5:22:5:23 | ControlFlowNode for y1 | ControlFlowNode for UnaryExpr | True | +| boolops.py | 5 | boolops.py:5:22:5:23 | ControlFlowNode for y1 | ControlFlowNode for z1 | False | +| boolops.py | 6 | boolops.py:6:22:6:23 | ControlFlowNode for z1 | ControlFlowNode for UnaryExpr | False | +| boolops.py | 6 | boolops.py:6:22:6:23 | ControlFlowNode for z1 | ControlFlowNode for UnaryExpr | True | +| boolops.py | 7 | boolops.py:7:8:11:23 | ControlFlowNode for UnaryExpr | ControlFlowNode for Str | False | +| boolops.py | 7 | boolops.py:7:8:11:23 | ControlFlowNode for UnaryExpr | ControlFlowNode for Str | True | +| boolops.py | 8 | boolops.py:8:12:8:12 | ControlFlowNode for x | ControlFlowNode for BoolExpr | False | +| boolops.py | 8 | boolops.py:8:12:8:12 | ControlFlowNode for x | ControlFlowNode for UnaryExpr | True | +| boolops.py | 9 | boolops.py:9:15:11:22 | ControlFlowNode for UnaryExpr | ControlFlowNode for UnaryExpr | False | +| boolops.py | 9 | boolops.py:9:15:11:22 | ControlFlowNode for UnaryExpr | ControlFlowNode for UnaryExpr | True | +| boolops.py | 10 | boolops.py:10:20:10:21 | ControlFlowNode for y2 | ControlFlowNode for UnaryExpr | False | +| boolops.py | 10 | boolops.py:10:20:10:21 | ControlFlowNode for y2 | ControlFlowNode for z2 | True | +| boolops.py | 11 | boolops.py:11:20:11:21 | ControlFlowNode for z2 | ControlFlowNode for UnaryExpr | False | +| boolops.py | 11 | boolops.py:11:20:11:21 | ControlFlowNode for z2 | ControlFlowNode for UnaryExpr | True | +| true_false_test.py | 3 | true_false_test.py:3:8:3:12 | ControlFlowNode for cond1 | ControlFlowNode for cond2 | False | +| true_false_test.py | 3 | true_false_test.py:3:8:3:12 | ControlFlowNode for cond1 | ControlFlowNode for true1 | True | +| true_false_test.py | 5 | true_false_test.py:5:8:5:12 | ControlFlowNode for cond2 | ControlFlowNode for Pass | True | +| true_false_test.py | 5 | true_false_test.py:5:8:5:12 | ControlFlowNode for cond2 | ControlFlowNode for false2 | False | +| true_false_test.py | 9 | true_false_test.py:9:8:9:12 | ControlFlowNode for cond3 | ControlFlowNode for false3 | False | +| true_false_test.py | 9 | true_false_test.py:9:8:9:12 | ControlFlowNode for cond3 | ControlFlowNode for true3 | True | +| true_false_test.py | 14 | true_false_test.py:14:12:14:16 | ControlFlowNode for cond4 | ControlFlowNode for false4 | False | +| true_false_test.py | 14 | true_false_test.py:14:12:14:16 | ControlFlowNode for cond4 | ControlFlowNode for true4 | True | +| true_false_test.py | 20 | true_false_test.py:20:8:20:12 | ControlFlowNode for cond5 | ControlFlowNode for Try | True | +| true_false_test.py | 20 | true_false_test.py:20:8:20:12 | ControlFlowNode for cond5 | ControlFlowNode for false5 | False | +| true_false_test.py | 27 | true_false_test.py:27:8:27:12 | ControlFlowNode for cond6 | ControlFlowNode for cond7 | True | +| true_false_test.py | 27 | true_false_test.py:27:8:27:12 | ControlFlowNode for cond6 | ControlFlowNode for false6 | False | +| true_false_test.py | 28 | true_false_test.py:28:12:28:16 | ControlFlowNode for cond7 | ControlFlowNode for false7 | False | +| true_false_test.py | 28 | true_false_test.py:28:12:28:16 | ControlFlowNode for cond7 | ControlFlowNode for true7 | True | +| true_false_test.py | 34 | true_false_test.py:34:8:34:12 | ControlFlowNode for cond8 | ControlFlowNode for false8 | False | +| true_false_test.py | 34 | true_false_test.py:34:8:34:12 | ControlFlowNode for cond8 | ControlFlowNode for range | True | +| true_false_test.py | 39 | true_false_test.py:39:8:39:12 | ControlFlowNode for cond9 | ControlFlowNode for While | True | +| true_false_test.py | 39 | true_false_test.py:39:8:39:12 | ControlFlowNode for cond9 | ControlFlowNode for false9 | False | +| true_false_test.py | 40 | true_false_test.py:40:15:40:20 | ControlFlowNode for cond10 | ControlFlowNode for false10 | False | +| true_false_test.py | 40 | true_false_test.py:40:15:40:20 | ControlFlowNode for cond10 | ControlFlowNode for true10 | True | +| true_false_test.py | 45 | true_false_test.py:45:11:45:11 | ControlFlowNode for IntegerLiteral | ControlFlowNode for cond12 | True | +| true_false_test.py | 46 | true_false_test.py:46:12:46:17 | ControlFlowNode for cond12 | ControlFlowNode for Try | True | +| true_false_test.py | 46 | true_false_test.py:46:12:46:17 | ControlFlowNode for cond12 | ControlFlowNode for While | False | +| true_false_test.py | 55 | true_false_test.py:55:11:55:16 | ControlFlowNode for condw1 | ControlFlowNode for truew2 | True | +| true_false_test.py | 55 | true_false_test.py:55:11:55:16 | ControlFlowNode for condw1 | Exit node for Function func2 | False | +| true_false_test.py | 59 | true_false_test.py:59:8:59:13 | ControlFlowNode for condi1 | ControlFlowNode for truei1 | True | +| true_false_test.py | 59 | true_false_test.py:59:8:59:13 | ControlFlowNode for condi1 | Exit node for Function func3 | False | +| true_false_test.py | 63 | true_false_test.py:63:11:63:14 | ControlFlowNode for True | ControlFlowNode for no_branch | True | +| true_false_test.py | 69 | true_false_test.py:69:11:69:14 | ControlFlowNode for True | ControlFlowNode for Break | True | +| true_false_test.py | 71 | true_false_test.py:71:8:71:13 | ControlFlowNode for cond11 | ControlFlowNode for true11 | True | +| true_false_test.py | 71 | true_false_test.py:71:8:71:13 | ControlFlowNode for cond11 | Exit node for Function func5 | False | +| true_false_test.py | 75 | true_false_test.py:75:8:75:13 | ControlFlowNode for cond13 | ControlFlowNode for cond13a | False | +| true_false_test.py | 75 | true_false_test.py:75:8:75:13 | ControlFlowNode for cond13 | ControlFlowNode for true13 | True | +| true_false_test.py | 75 | true_false_test.py:75:18:75:24 | ControlFlowNode for cond13a | ControlFlowNode for BoolExpr | False | +| true_false_test.py | 75 | true_false_test.py:75:18:75:24 | ControlFlowNode for cond13a | ControlFlowNode for true13 | True | +| true_false_test.py | 77 | true_false_test.py:77:8:77:13 | ControlFlowNode for cond14 | ControlFlowNode for cond14a | True | +| true_false_test.py | 77 | true_false_test.py:77:8:77:13 | ControlFlowNode for cond14 | ControlFlowNode for cond15 | False | +| true_false_test.py | 77 | true_false_test.py:77:19:77:25 | ControlFlowNode for cond14a | ControlFlowNode for cond15 | False | +| true_false_test.py | 77 | true_false_test.py:77:19:77:25 | ControlFlowNode for cond14a | ControlFlowNode for true14 | True | +| true_false_test.py | 79 | true_false_test.py:79:15:79:20 | ControlFlowNode for cond15 | ControlFlowNode for false15 | False | +| true_false_test.py | 79 | true_false_test.py:79:15:79:20 | ControlFlowNode for cond15 | ControlFlowNode for true15 | True | +| true_false_test.py | 80 | true_false_test.py:80:15:80:20 | ControlFlowNode for cond16 | ControlFlowNode for cond17 | False | +| true_false_test.py | 80 | true_false_test.py:80:15:80:20 | ControlFlowNode for cond16 | ControlFlowNode for true16 | True | +| true_false_test.py | 80 | true_false_test.py:80:25:80:30 | ControlFlowNode for cond17 | ControlFlowNode for false16 | False | +| true_false_test.py | 80 | true_false_test.py:80:25:80:30 | ControlFlowNode for cond17 | ControlFlowNode for true16 | True | +| true_false_test.py | 81 | true_false_test.py:81:15:81:20 | ControlFlowNode for cond18 | ControlFlowNode for cond19 | True | +| true_false_test.py | 81 | true_false_test.py:81:15:81:20 | ControlFlowNode for cond18 | ControlFlowNode for false18 | False | +| true_false_test.py | 81 | true_false_test.py:81:26:81:31 | ControlFlowNode for cond19 | ControlFlowNode for false18 | False | +| true_false_test.py | 81 | true_false_test.py:81:26:81:31 | ControlFlowNode for cond19 | ControlFlowNode for true18 | True | +| true_false_test.py | 84 | true_false_test.py:84:11:84:16 | ControlFlowNode for cond20 | ControlFlowNode for Yield | True | +| true_false_test.py | 84 | true_false_test.py:84:11:84:16 | ControlFlowNode for cond20 | ControlFlowNode for cond21 | False | +| true_false_test.py | 84 | true_false_test.py:84:21:84:26 | ControlFlowNode for cond21 | ControlFlowNode for Yield | True | +| true_false_test.py | 84 | true_false_test.py:84:21:84:26 | ControlFlowNode for cond21 | ControlFlowNode for cond22 | False | +| true_false_test.py | 85 | true_false_test.py:85:11:85:16 | ControlFlowNode for cond23 | ControlFlowNode for Yield | False | +| true_false_test.py | 85 | true_false_test.py:85:11:85:16 | ControlFlowNode for cond23 | ControlFlowNode for cond24 | True | +| true_false_test.py | 85 | true_false_test.py:85:22:85:27 | ControlFlowNode for cond24 | ControlFlowNode for Yield | False | +| true_false_test.py | 85 | true_false_test.py:85:22:85:27 | ControlFlowNode for cond24 | ControlFlowNode for cond25 | True | +| true_succ.py | 4 | true_succ.py:4:8:4:15 | ControlFlowNode for filename | ControlFlowNode for Str | False | +| true_succ.py | 4 | true_succ.py:4:8:4:15 | ControlFlowNode for filename | ControlFlowNode for Try | True | +| true_succ.py | 13 | true_succ.py:13:16:13:28 | ControlFlowNode for Compare | ControlFlowNode for Str | False | +| true_succ.py | 13 | true_succ.py:13:16:13:28 | ControlFlowNode for Compare | ControlFlowNode for f | True | +| true_succ.py | 13 | true_succ.py:13:16:13:28 | ControlFlowNode for Compare | ControlFlowNode for f | True | diff --git a/python/ql/test/library-tests/ControlFlow/truefalse/TrueFalseSuccessors.ql b/python/ql/test/library-tests/ControlFlow/truefalse/TrueFalseSuccessors.ql new file mode 100644 index 000000000000..0f0e614523a3 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/truefalse/TrueFalseSuccessors.ql @@ -0,0 +1,15 @@ +/** + * @name TrueFalseSuccessors Test + * @description Tests true/false successors + * @kind problem + * @problem.severity warning + */ + +import python + +from ControlFlowNode p, ControlFlowNode s, string which +where +s = p.getAFalseSuccessor() and which = "False" +or +s = p.getATrueSuccessor() and which = "True" +select p.getLocation().getFile().getShortName(), p.getLocation().getStartLine(), p, s.toString(), which diff --git a/python/ql/test/library-tests/ControlFlow/truefalse/boolops.py b/python/ql/test/library-tests/ControlFlow/truefalse/boolops.py new file mode 100644 index 000000000000..383b12a6a567 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/truefalse/boolops.py @@ -0,0 +1,14 @@ +def boolops(x, y1, z1, y2, z2): + p = not( + x + and not ( + y1 or + z1)) + if not( + x + or not ( + y2 and + z2)): + return b"true" + else: + return b"false" diff --git a/python/ql/test/library-tests/ControlFlow/truefalse/true_false_test.py b/python/ql/test/library-tests/ControlFlow/truefalse/true_false_test.py new file mode 100644 index 000000000000..031845551ae4 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/truefalse/true_false_test.py @@ -0,0 +1,85 @@ + +def func(): + if cond1: + true1 + if cond2: + pass + else: + false2 + if cond3: + true3 + else: + false3 + try: + if cond4: + true4() + else: + false4() + finally: + pass + if cond5: + try: + true5() + except: + pass + else: + false5 + if cond6: + if cond7: + true7 + else: + false7 + else: + false6 + if cond8: + for i in range(10): + pass + else: + false8 + if cond9: + while cond10: + true10 + false10 + else: + false9 + while 1: + if cond12: + try: + true12() + except IOError: + true12 = 0 + + + +def func2(): + while condw1: + truew2 + +def func3(): + if condi1: + truei1 + +def func4(): + while True: + no_branch + if unreachable: + not reachable + +def func5(): + while True: + break + if cond11: + true11 + +def func6(): + if cond13 or cond13a: + true13 + if cond14 and cond14a: + true14 + true15 if cond15 else false15 + true16 if cond16 or cond17 else false16 + true18 if cond18 and cond19 else false18 + +def func7(): + yield cond20 or cond21 or cond22 + yield cond23 and cond24 and cond25 diff --git a/python/ql/test/library-tests/ControlFlow/truefalse/true_succ.py b/python/ql/test/library-tests/ControlFlow/truefalse/true_succ.py new file mode 100644 index 000000000000..1d879cd4b429 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/truefalse/true_succ.py @@ -0,0 +1,16 @@ +#https://semmle.com/jira/browse/ODASA-1222 + +def example(filename): + if filename: + try: + f = None + try: + f = open(filename, 'w') + f.write('Hello') + except IOError: + sys.exit(1) + finally: + if f is not None: f.close() + + assert u"This is a false successor to the comparison" + diff --git a/python/ql/test/library-tests/ControlFlow/try/test.py b/python/ql/test/library-tests/ControlFlow/try/test.py new file mode 100644 index 000000000000..7c543b2993e0 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/try/test.py @@ -0,0 +1,42 @@ +#Test that the flow control through nested trys is handled correctly. + +def f1(): + try: + x = call() + finally: + try: + another_call() + except: + pass + return x + +def f2(): + try: + x = call() + except: + try: + another_call() + finally: + x = 0 + return x + +def f3(): + try: + x = call() + except: + try: + another_call() + except: + pass + return x + +def f4(): + try: + x = call() + finally: + try: + another_call() + finally: + x = 0 + return x + diff --git a/python/ql/test/library-tests/ControlFlow/try/test_ssa.expected b/python/ql/test/library-tests/ControlFlow/try/test_ssa.expected new file mode 100644 index 000000000000..73dd6613cec5 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/try/test_ssa.expected @@ -0,0 +1,8 @@ +| test.py | SSA Variable f1 | 3 | Exit node for Module test | 0 | +| test.py | SSA Variable f2 | 13 | Exit node for Module test | 0 | +| test.py | SSA Variable f3 | 23 | Exit node for Module test | 0 | +| test.py | SSA Variable f4 | 33 | Exit node for Module test | 0 | +| test.py | SSA Variable x | 5 | ControlFlowNode for x | 11 | +| test.py | SSA Variable x | 21 | ControlFlowNode for x | 21 | +| test.py | SSA Variable x | 31 | ControlFlowNode for x | 31 | +| test.py | SSA Variable x | 40 | ControlFlowNode for x | 41 | diff --git a/python/ql/test/library-tests/ControlFlow/try/test_ssa.ql b/python/ql/test/library-tests/ControlFlow/try/test_ssa.ql new file mode 100644 index 000000000000..8df422495fb5 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/try/test_ssa.ql @@ -0,0 +1,7 @@ +import python + +from SsaVariable var, ControlFlowNode use +where use = var.getAUse() +select var.getLocation().getFile().getShortName(), +var.toString(), var.getLocation().getStartLine(), use.toString(), use.getLocation().getStartLine() + diff --git a/python/ql/test/library-tests/DefUse/Definitions.expected b/python/ql/test/library-tests/DefUse/Definitions.expected new file mode 100644 index 000000000000..a4670930f96e --- /dev/null +++ b/python/ql/test/library-tests/DefUse/Definitions.expected @@ -0,0 +1,5 @@ +| a | 1 | +| b | 2 | +| c | 3 | +| ctx | 22 | +| ex | 16 | diff --git a/python/ql/test/library-tests/DefUse/Definitions.ql b/python/ql/test/library-tests/DefUse/Definitions.ql new file mode 100644 index 000000000000..927aee8f930e --- /dev/null +++ b/python/ql/test/library-tests/DefUse/Definitions.ql @@ -0,0 +1,12 @@ +/** + * @name Definitions + * @description Insert description here... + * @kind problem + * @problem.severity warning + */ + +import python + +from Name d +where d.defines(_) +select d.getId(), d.getLocation().getStartLine() \ No newline at end of file diff --git a/python/ql/test/library-tests/DefUse/Uses.expected b/python/ql/test/library-tests/DefUse/Uses.expected new file mode 100644 index 000000000000..d36da6087a56 --- /dev/null +++ b/python/ql/test/library-tests/DefUse/Uses.expected @@ -0,0 +1,9 @@ +| C1 | 19 | +| C2 | 22 | +| E1 | 11 | +| E2 | 16 | +| a | 4 | +| b | 5 | +| ctx | 23 | +| d | 7 | +| ex | 17 | diff --git a/python/ql/test/library-tests/DefUse/Uses.ql b/python/ql/test/library-tests/DefUse/Uses.ql new file mode 100644 index 000000000000..f19f08c126ec --- /dev/null +++ b/python/ql/test/library-tests/DefUse/Uses.ql @@ -0,0 +1,12 @@ +/** + * @name Usages + * @description Insert description here... + * @kind problem + * @problem.severity warning + */ + +import python + +from Name u +where u.uses(_) +select u.getId(), u.getLocation().getStartLine() \ No newline at end of file diff --git a/python/ql/test/library-tests/DefUse/defuse.py b/python/ql/test/library-tests/DefUse/defuse.py new file mode 100644 index 000000000000..715a8d29007f --- /dev/null +++ b/python/ql/test/library-tests/DefUse/defuse.py @@ -0,0 +1,24 @@ +a = 1 +b = 2 +c = 3 +a +b +# c is not used +d # not defined + +try: + pass +except E1: + pass + +try: + pass +except E2 as ex: + ex + +with C1: + pass + +with C2 as ctx: + ctx + diff --git a/python/ql/test/library-tests/DuplicateCode/Duplicate.expected b/python/ql/test/library-tests/DuplicateCode/Duplicate.expected new file mode 100644 index 000000000000..5c7717546d66 --- /dev/null +++ b/python/ql/test/library-tests/DuplicateCode/Duplicate.expected @@ -0,0 +1,2 @@ +| Duplicate code: 34 duplicated lines. | Duplicate code: 34 duplicated lines. | duplicate_test.py | 9 | 42 | +| Duplicate code: 80 duplicated lines. | Duplicate code: 80 duplicated lines. | duplicate_test.py | 84 | 163 | diff --git a/python/ql/test/library-tests/DuplicateCode/Duplicate.ql b/python/ql/test/library-tests/DuplicateCode/Duplicate.ql new file mode 100644 index 000000000000..3368fef9d16e --- /dev/null +++ b/python/ql/test/library-tests/DuplicateCode/Duplicate.ql @@ -0,0 +1,21 @@ +/** + * @name Duplicate + * @description Insert description here... + * @kind problem + * @problem.severity warning + */ + +import python + +import external.CodeDuplication + +predicate lexically_sorted(DuplicateBlock dup1, DuplicateBlock dup2) { + dup1.sourceFile().getName() < dup2.sourceFile().getName() + or + dup1.sourceFile().getName() = dup2.sourceFile().getName() and dup1.sourceStartLine() < dup2.sourceStartLine() +} + +from DuplicateBlock dup1, DuplicateBlock dup2 +where dup1.getEquivalenceClass() = dup2.getEquivalenceClass() +and lexically_sorted(dup1, dup2) +select dup1.toString(), dup2.toString(), dup1.sourceFile().getShortName(), dup1.sourceStartLine(), dup1.sourceEndLine() \ No newline at end of file diff --git a/python/ql/test/library-tests/DuplicateCode/DuplicateStatements.expected b/python/ql/test/library-tests/DuplicateCode/DuplicateStatements.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/DuplicateCode/DuplicateStatements.ql b/python/ql/test/library-tests/DuplicateCode/DuplicateStatements.ql new file mode 100644 index 000000000000..a4243bca968f --- /dev/null +++ b/python/ql/test/library-tests/DuplicateCode/DuplicateStatements.ql @@ -0,0 +1,25 @@ +/** + * @name DuplicateStatements + * @description Insert description here... + * @kind problem + * @problem.severity warning + */ + +import python +import external.CodeDuplication + +predicate mostlyDuplicateFunction(Function f) { + exists(int covered, int total, Function other, int percent | + duplicateStatements(f, other, covered, total) and + covered != total and + total > 5 and + covered * 100 / total = percent and + percent > 80 and + not exists(Scope s | s = f.getScope*() | duplicateScopes(s, _, _, _)) + ) +} + +from Stmt s +where mostlyDuplicateFunction(s.getScope()) and +not duplicateStatement(s.getScope(), _, s, _) +select s.toString(), s.getLocation().toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/DuplicateCode/Similar.expected b/python/ql/test/library-tests/DuplicateCode/Similar.expected new file mode 100644 index 000000000000..34e0b8f745cf --- /dev/null +++ b/python/ql/test/library-tests/DuplicateCode/Similar.expected @@ -0,0 +1,23 @@ +| duplicate_test.py:9:1:20:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py:47:1:58:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py | 9 | 20 | +| duplicate_test.py:9:1:20:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py:249:1:260:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py | 9 | 20 | +| duplicate_test.py:9:1:20:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py:287:1:298:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py | 9 | 20 | +| duplicate_test.py:14:8:25:13 | Similar code: 12 almost duplicated lines. | duplicate_test.py:52:8:63:13 | Similar code: 12 almost duplicated lines. | duplicate_test.py | 14 | 25 | +| duplicate_test.py:14:8:25:13 | Similar code: 12 almost duplicated lines. | duplicate_test.py:254:8:265:13 | Similar code: 12 almost duplicated lines. | duplicate_test.py | 14 | 25 | +| duplicate_test.py:20:28:42:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py:58:28:80:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py | 20 | 42 | +| duplicate_test.py:20:28:42:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py:260:28:282:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py | 20 | 42 | +| duplicate_test.py:20:28:42:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py:296:40:318:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py | 20 | 42 | +| duplicate_test.py:36:1:47:0 | Similar code: 12 almost duplicated lines. | duplicate_test.py:74:1:84:0 | Similar code: 11 almost duplicated lines. | duplicate_test.py | 36 | 47 | +| duplicate_test.py:36:1:47:0 | Similar code: 12 almost duplicated lines. | duplicate_test.py:276:1:287:0 | Similar code: 12 almost duplicated lines. | duplicate_test.py | 36 | 47 | +| duplicate_test.py:36:22:56:26 | Similar code: 21 almost duplicated lines. | duplicate_test.py:276:21:296:26 | Similar code: 21 almost duplicated lines. | duplicate_test.py | 36 | 56 | +| duplicate_test.py:42:22:57:9 | Similar code: 16 almost duplicated lines. | duplicate_test.py:245:20:259:9 | Similar code: 15 almost duplicated lines. | duplicate_test.py | 42 | 57 | +| duplicate_test.py:42:22:57:9 | Similar code: 16 almost duplicated lines. | duplicate_test.py:282:22:297:9 | Similar code: 16 almost duplicated lines. | duplicate_test.py | 42 | 57 | +| duplicate_test.py:47:1:58:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py:249:1:260:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py | 47 | 58 | +| duplicate_test.py:47:1:58:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py:287:1:298:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py | 47 | 58 | +| duplicate_test.py:52:8:63:13 | Similar code: 12 almost duplicated lines. | duplicate_test.py:254:8:265:13 | Similar code: 12 almost duplicated lines. | duplicate_test.py | 52 | 63 | +| duplicate_test.py:58:28:80:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py:260:28:282:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py | 58 | 80 | +| duplicate_test.py:58:28:80:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py:296:40:318:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py | 58 | 80 | +| duplicate_test.py:74:1:84:0 | Similar code: 11 almost duplicated lines. | duplicate_test.py:276:1:287:0 | Similar code: 12 almost duplicated lines. | duplicate_test.py | 74 | 84 | +| duplicate_test.py:82:25:163:24 | Similar code: 82 almost duplicated lines. | duplicate_test.py:163:24:245:24 | Similar code: 83 almost duplicated lines. | duplicate_test.py | 82 | 163 | +| duplicate_test.py:245:20:259:9 | Similar code: 15 almost duplicated lines. | duplicate_test.py:282:22:297:9 | Similar code: 16 almost duplicated lines. | duplicate_test.py | 245 | 259 | +| duplicate_test.py:249:1:260:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py:287:1:298:17 | Similar code: 12 almost duplicated lines. | duplicate_test.py | 249 | 260 | +| duplicate_test.py:260:28:282:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py:296:40:318:31 | Similar code: 23 almost duplicated lines. | duplicate_test.py | 260 | 282 | diff --git a/python/ql/test/library-tests/DuplicateCode/Similar.ql b/python/ql/test/library-tests/DuplicateCode/Similar.ql new file mode 100644 index 000000000000..c055551793e8 --- /dev/null +++ b/python/ql/test/library-tests/DuplicateCode/Similar.ql @@ -0,0 +1,21 @@ +/** + * @name Similar + * @description Insert description here... + * @kind problem + * @problem.severity warning + */ + +import python + +import external.CodeDuplication + +predicate lexically_sorted(SimilarBlock dup1, SimilarBlock dup2) { + dup1.sourceFile().getName() < dup2.sourceFile().getName() + or + dup1.sourceFile().getName() = dup2.sourceFile().getName() and dup1.sourceStartLine() < dup2.sourceStartLine() +} + +from SimilarBlock dup1, SimilarBlock dup2 +where dup1.getEquivalenceClass() = dup2.getEquivalenceClass() +and lexically_sorted(dup1, dup2) +select dup1, dup2, dup1.sourceFile().getShortName(), dup1.sourceStartLine(), dup1.sourceEndLine() \ No newline at end of file diff --git a/python/ql/test/library-tests/DuplicateCode/duplicate_test.py b/python/ql/test/library-tests/DuplicateCode/duplicate_test.py new file mode 100644 index 000000000000..a382fdefcef2 --- /dev/null +++ b/python/ql/test/library-tests/DuplicateCode/duplicate_test.py @@ -0,0 +1,321 @@ +#Code Duplication + + +#Exact duplication of function + +#Code copied from stdlib, copyright PSF. +#See http://www.python.org/download/releases/2.7/license/ + +def dis(x=None): + """Disassemble classes, methods, functions, or code. + + With no argument, disassemble the last traceback. + + """ + if x is None: + distb() + return + if isinstance(x, types.InstanceType): + x = x.__class__ + if hasattr(x, 'im_func'): + x = x.im_func + if hasattr(x, 'func_code'): + x = x.func_code + if hasattr(x, '__dict__'): + items = x.__dict__.items() + items.sort() + for name, x1 in items: + if isinstance(x1, _have_code): + print "Disassembly of %s:" % name + try: + dis(x1) + except TypeError, msg: + print "Sorry:", msg + print + elif hasattr(x, 'co_code'): + disassemble(x) + elif isinstance(x, str): + disassemble_string(x) + else: + raise TypeError, \ + "don't know how to disassemble %s objects" % \ + type(x).__name__ + + +#And duplicate version + +def dis2(x=None): + """Disassemble classes, methods, functions, or code. + + With no argument, disassemble the last traceback. + + """ + if x is None: + distb() + return + if isinstance(x, types.InstanceType): + x = x.__class__ + if hasattr(x, 'im_func'): + x = x.im_func + if hasattr(x, 'func_code'): + x = x.func_code + if hasattr(x, '__dict__'): + items = x.__dict__.items() + items.sort() + for name, x1 in items: + if isinstance(x1, _have_code): + print "Disassembly of %s:" % name + try: + dis(x1) + except TypeError, msg: + print "Sorry:", msg + print + elif hasattr(x, 'co_code'): + disassemble(x) + elif isinstance(x, str): + disassemble_string(x) + else: + raise TypeError, \ + "don't know how to disassemble %s objects" % \ + type(x).__name__ + +#Exactly duplicate class + +class Popen3: + """Class representing a child process. Normally, instances are created + internally by the functions popen2() and popen3().""" + + sts = -1 # Child not completed yet + + def __init__(self, cmd, capturestderr=False, bufsize=-1): + """The parameter 'cmd' is the shell command to execute in a + sub-process. On UNIX, 'cmd' may be a sequence, in which case arguments + will be passed directly to the program without shell intervention (as + with os.spawnv()). If 'cmd' is a string it will be passed to the shell + (as with os.system()). The 'capturestderr' flag, if true, specifies + that the object should capture standard error output of the child + process. The default is false. If the 'bufsize' parameter is + specified, it specifies the size of the I/O buffers to/from the child + process.""" + _cleanup() + self.cmd = cmd + p2cread, p2cwrite = os.pipe() + c2pread, c2pwrite = os.pipe() + if capturestderr: + errout, errin = os.pipe() + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + if capturestderr: + os.dup2(errin, 2) + self._run_child(cmd) + os.close(p2cread) + self.tochild = os.fdopen(p2cwrite, 'w', bufsize) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) + if capturestderr: + os.close(errin) + self.childerr = os.fdopen(errout, 'r', bufsize) + else: + self.childerr = None + + def __del__(self): + # In case the child hasn't been waited on, check if it's done. + self.poll(_deadstate=sys.maxint) + if self.sts < 0: + if _active is not None: + # Child is still running, keep us alive until we can wait on it. + _active.append(self) + + def _run_child(self, cmd): + if isinstance(cmd, basestring): + cmd = ['/bin/sh', '-c', cmd] + os.closerange(3, MAXFD) + try: + os.execvp(cmd[0], cmd) + finally: + os._exit(1) + + def poll(self, _deadstate=None): + """Return the exit status of the child process if it has finished, + or -1 if it hasn't finished yet.""" + if self.sts < 0: + try: + pid, sts = os.waitpid(self.pid, os.WNOHANG) + # pid will be 0 if self.pid hasn't terminated + if pid == self.pid: + self.sts = sts + except os.error: + if _deadstate is not None: + self.sts = _deadstate + return self.sts + + def wait(self): + """Wait for and return the exit status of the child process.""" + if self.sts < 0: + pid, sts = os.waitpid(self.pid, 0) + # This used to be a test, but it is believed to be + # always true, so I changed it to an assertion - mvl + assert pid == self.pid + self.sts = sts + return self.sts + + +class Popen3Again: + """Class representing a child process. Normally, instances are created + internally by the functions popen2() and popen3().""" + + sts = -1 # Child not completed yet + + def __init__(self, cmd, capturestderr=False, bufsize=-1): + """The parameter 'cmd' is the shell command to execute in a + sub-process. On UNIX, 'cmd' may be a sequence, in which case arguments + will be passed directly to the program without shell intervention (as + with os.spawnv()). If 'cmd' is a string it will be passed to the shell + (as with os.system()). The 'capturestderr' flag, if true, specifies + that the object should capture standard error output of the child + process. The default is false. If the 'bufsize' parameter is + specified, it specifies the size of the I/O buffers to/from the child + process.""" + _cleanup() + self.cmd = cmd + p2cread, p2cwrite = os.pipe() + c2pread, c2pwrite = os.pipe() + if capturestderr: + errout, errin = os.pipe() + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + if capturestderr: + os.dup2(errin, 2) + self._run_child(cmd) + os.close(p2cread) + self.tochild = os.fdopen(p2cwrite, 'w', bufsize) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) + if capturestderr: + os.close(errin) + self.childerr = os.fdopen(errout, 'r', bufsize) + else: + self.childerr = None + + def __del__(self): + # In case the child hasn't been waited on, check if it's done. + self.poll(_deadstate=sys.maxint) + if self.sts < 0: + if _active is not None: + # Child is still running, keep us alive until we can wait on it. + _active.append(self) + + def _run_child(self, cmd): + if isinstance(cmd, basestring): + cmd = ['/bin/sh', '-c', cmd] + os.closerange(3, MAXFD) + try: + os.execvp(cmd[0], cmd) + finally: + os._exit(1) + + def poll(self, _deadstate=None): + """Return the exit status of the child process if it has finished, + or -1 if it hasn't finished yet.""" + if self.sts < 0: + try: + pid, sts = os.waitpid(self.pid, os.WNOHANG) + # pid will be 0 if self.pid hasn't terminated + if pid == self.pid: + self.sts = sts + except os.error: + if _deadstate is not None: + self.sts = _deadstate + return self.sts + + def wait(self): + """Wait for and return the exit status of the child process.""" + if self.sts < 0: + pid, sts = os.waitpid(self.pid, 0) + # This used to be a test, but it is believed to be + # always true, so I changed it to an assertion - mvl + assert pid == self.pid + self.sts = sts + return self.sts + +#Duplicate function with identifiers changed + +def dis3(y=None): + """frobnicate classes, methods, functions, or code. + + With no argument, frobnicate the last traceback. + + """ + if y is None: + distb() + return + if isinstance(y, types.InstanceType): + y = y.__class__ + if hasattr(y, 'im_func'): + y = y.im_func + if hasattr(y, 'func_code'): + y = y.func_code + if hasattr(y, '__dict__'): + items = y.__dict__.items() + items.sort() + for name, y1 in items: + if isinstance(y1, _have_code): + print "Disassembly of %s:" % name + try: + dis(y1) + except TypeError, msg: + print "Sorry:", msg + print + elif hasattr(y, 'co_code'): + frobnicate(y) + elif isinstance(y, str): + frobnicate_string(y) + else: + raise TypeError, \ + "don't know how to frobnicate %s objects" % \ + type(y).__name__ + + +#Mostly similar function with changed identifiers + +def dis5(z=None): + """splat classes, methods, functions, or code. + + With no argument, splat the last traceback. + + """ + if z is None: + distb() + return + if isinstance(z, types.InstanceType): + z = z.__class__ + if hasattr(y, 'func_code'): + y = y.func_code + if hasattr(z, '__dict__'): + items = z.__dict__.items() + items.sort() + for name, z1 in items: + if isinstance(z1, _have_code): + print "Disassembly of %s:" % name + try: + dis(z1) + except TypeError, msg: + print "Sorry:", msg + print + elif hasattr(z, 'co_code'): + splat(z) + elif isinstance(z, str): + splat_string(z) + else: + raise TypeError, \ + "don't know how to splat %s objects" % \ + type(z).__name__ + + + diff --git a/python/ql/test/library-tests/PointsTo/calls/Argument.expected b/python/ql/test/library-tests/PointsTo/calls/Argument.expected new file mode 100644 index 000000000000..dbc7e586f472 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/calls/Argument.expected @@ -0,0 +1,15 @@ +| 19 | 0 | ControlFlowNode for w | Function f | +| 19 | 1 | ControlFlowNode for x | Function f | +| 19 | 2 | ControlFlowNode for y | Function f | +| 21 | 0 | ControlFlowNode for y | Function f | +| 21 | 1 | ControlFlowNode for w | Function f | +| 21 | 2 | ControlFlowNode for z | Function f | +| 23 | 0 | ControlFlowNode for c | Function f | +| 23 | 1 | ControlFlowNode for w | Function f | +| 23 | 2 | ControlFlowNode for z | Function f | +| 24 | 0 | ControlFlowNode for c | Function n | +| 24 | 1 | ControlFlowNode for x | Function n | +| 25 | 0 | ControlFlowNode for y | Function n | +| 25 | 1 | ControlFlowNode for z | Function n | +| 33 | 0 | ControlFlowNode for IntegerLiteral | Function foo | +| 34 | 0 | ControlFlowNode for IntegerLiteral | Function foo | diff --git a/python/ql/test/library-tests/PointsTo/calls/Argument.ql b/python/ql/test/library-tests/PointsTo/calls/Argument.ql new file mode 100644 index 000000000000..e88baf757912 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/calls/Argument.ql @@ -0,0 +1,5 @@ +import python + +from ControlFlowNode arg, FunctionObject func, int i +where arg = func.getArgumentForCall(_, i) +select arg.getLocation().getStartLine(), i, arg.toString(), func.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/calls/Call.expected b/python/ql/test/library-tests/PointsTo/calls/Call.expected new file mode 100644 index 000000000000..9e9c5646d893 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/calls/Call.expected @@ -0,0 +1,7 @@ +| 19 | ControlFlowNode for f() | Function f | +| 21 | ControlFlowNode for f() | Function f | +| 23 | ControlFlowNode for Attribute() | Function f | +| 24 | ControlFlowNode for Attribute() | Function n | +| 25 | ControlFlowNode for Attribute() | Function n | +| 33 | ControlFlowNode for Attribute() | Function foo | +| 34 | ControlFlowNode for Attribute() | Function foo | diff --git a/python/ql/test/library-tests/PointsTo/calls/Call.ql b/python/ql/test/library-tests/PointsTo/calls/Call.ql new file mode 100644 index 000000000000..d1cfbdad6903 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/calls/Call.ql @@ -0,0 +1,7 @@ + +import python + +from ControlFlowNode call, FunctionObject func + +where call = func.getACall() +select call.getLocation().getStartLine(), call.toString(), func.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/calls/test.py b/python/ql/test/library-tests/PointsTo/calls/test.py new file mode 100644 index 000000000000..38667a4a6e13 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/calls/test.py @@ -0,0 +1,34 @@ + +def f(arg0, arg1, arg2): + pass + +class C(object): + + m = f + + def n(self, arg1): + pass + +w = 0 +x = 1 +y = 2 +z = 3 + +def calls(): + outer = False + f(w, x, y) + def inner(): + f(y, w, z) + c = C() + c.m(w, z) + c.n(x) + C.n(y, z) + +class D(object): + + @staticmethod + def foo(arg): + return arg + +D.foo(1) +D().foo(2) diff --git a/python/ql/test/library-tests/PointsTo/customise/test.expected b/python/ql/test/library-tests/PointsTo/customise/test.expected new file mode 100644 index 000000000000..739123d92c78 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/customise/test.expected @@ -0,0 +1,9 @@ +| 9 | ControlFlowNode for has_type_int | Function has_type_int | builtin-class function | +| 9 | ControlFlowNode for has_type_int() | has_type_int() | builtin-class int | +| 9 | ControlFlowNode for x | has_type_int() | builtin-class int | +| 10 | ControlFlowNode for has_type_float | Function has_type_float | builtin-class function | +| 10 | ControlFlowNode for has_type_float() | has_type_float() | builtin-class float | +| 10 | ControlFlowNode for y | has_type_float() | builtin-class float | +| 11 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | +| 11 | ControlFlowNode for x | has_type_int() | builtin-class int | +| 11 | ControlFlowNode for y | has_type_float() | builtin-class float | diff --git a/python/ql/test/library-tests/PointsTo/customise/test.py b/python/ql/test/library-tests/PointsTo/customise/test.py new file mode 100644 index 000000000000..f6aef15a9ffe --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/customise/test.py @@ -0,0 +1,11 @@ +def has_type_int(): + return untaceable() + +def has_type_float(): + return untaceable2() + +def test(): + #Ignore before this comment + x = has_type_int() + y = has_type_float() + return x, y \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/customise/test.ql b/python/ql/test/library-tests/PointsTo/customise/test.ql new file mode 100644 index 000000000000..dca091e2e4f4 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/customise/test.ql @@ -0,0 +1,35 @@ + +import python +import semmle.python.types.Extensions + +/* Customise: Claim any function called has_type_XXX return any class + * whose name matches XXX + */ +class HasTypeFact extends CustomPointsToOriginFact { + + HasTypeFact() { + exists(FunctionObject func, string name | + func.getACall() = this and + name = func.getName() and + name.prefix("has_type_".length()) = "has_type_" + ) + } + + override predicate pointsTo(Object value, ClassObject cls) { + exists(FunctionObject func, string name | + func.getACall() = this and + name = func.getName() and + name.prefix("has_type_".length()) = "has_type_" | + cls.getName() = name.suffix("has_type_".length()) + ) and + value = this + } + +} + + +from int line, ControlFlowNode f, Object o, ClassObject c +where f.getLocation().getStartLine() = line and + exists(Comment ct | ct.getLocation().getStartLine() < line) and + f.refersTo(o, c, _) +select line, f.toString(), o.toString(), c.toString() diff --git a/python/ql/test/library-tests/PointsTo/decorators/Test.expected b/python/ql/test/library-tests/PointsTo/decorators/Test.expected new file mode 100644 index 000000000000..136adce143ff --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/decorators/Test.expected @@ -0,0 +1,3 @@ +| 41 | ControlFlowNode for func1 | Function func1 | test.py:23 | +| 42 | ControlFlowNode for func2 | Function wrapper | test.py:10 | +| 43 | ControlFlowNode for func3 | Function wrapper | test.py:17 | diff --git a/python/ql/test/library-tests/PointsTo/decorators/Test.ql b/python/ql/test/library-tests/PointsTo/decorators/Test.ql new file mode 100644 index 000000000000..c873feb9b488 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/decorators/Test.ql @@ -0,0 +1,11 @@ +import python + +from ControlFlowNode f, Object o, ControlFlowNode x, int line + +where f.refersTo(o, x) and +f.getLocation().getFile().getBaseName() = "test.py" and +// We don't care about the internals of functools which vary from +// version to version, just the end result. +line = f.getLocation().getStartLine() and line > 40 + +select line, f.toString(), o.toString(), x.getLocation().toString() diff --git a/python/ql/test/library-tests/PointsTo/decorators/test.py b/python/ql/test/library-tests/PointsTo/decorators/test.py new file mode 100644 index 000000000000..1c83d17fef44 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/decorators/test.py @@ -0,0 +1,43 @@ +import functools + +def annotate(value): + def inner(func): + func.annotation = value + return func + return inner + +def wraps1(func): + def wrapper(*args): + res = func(*args) + return res + return wrapper + +def wraps2(func): + @functools.wraps(func) + def wrapper(*args): + res = func(*args) + return res + return wrapper + +@annotate(100) +def func1(): + pass + +@wraps1 +def func2(): + pass + +@wraps2 +def func3(): + pass + + + + + + + + +func1 +func2 +func3 \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/functions/Calls.expected b/python/ql/test/library-tests/PointsTo/functions/Calls.expected new file mode 100644 index 000000000000..23dc828d03f9 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/functions/Calls.expected @@ -0,0 +1,5 @@ +| 7 | ControlFlowNode for fail() | Function fail | function | +| 11 | ControlFlowNode for print() | Builtin-function print | function | +| 12 | ControlFlowNode for Attribute() | Builtin-function exit | function | +| 15 | ControlFlowNode for bar() | Function bar | function | +| 19 | ControlFlowNode for bar() | Function bar | function | diff --git a/python/ql/test/library-tests/PointsTo/functions/Calls.ql b/python/ql/test/library-tests/PointsTo/functions/Calls.ql new file mode 100644 index 000000000000..6f1e8cf8bd3a --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/functions/Calls.ql @@ -0,0 +1,12 @@ + +import python + +from CallNode call, FunctionObject func, string kind +where +(func.getAMethodCall() = call and kind = "method" + or + func.getAFunctionCall() = call and kind = "function" +) +and +call.getLocation().getFile().getShortName().matches("odasa%") +select call.getLocation().getStartLine(), call.toString(), func.toString(), kind diff --git a/python/ql/test/library-tests/PointsTo/functions/NeverReturns.expected b/python/ql/test/library-tests/PointsTo/functions/NeverReturns.expected new file mode 100644 index 000000000000..41ad8f89bc74 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/functions/NeverReturns.expected @@ -0,0 +1,2 @@ +| Builtin-function exit | +| Function fail | diff --git a/python/ql/test/library-tests/PointsTo/functions/NeverReturns.ql b/python/ql/test/library-tests/PointsTo/functions/NeverReturns.ql new file mode 100644 index 000000000000..ebb69fc7a0f3 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/functions/NeverReturns.ql @@ -0,0 +1,6 @@ + +import python + +from FunctionObject f +where f.neverReturns() +select f.toString() diff --git a/python/ql/test/library-tests/PointsTo/functions/odasa6418.py b/python/ql/test/library-tests/PointsTo/functions/odasa6418.py new file mode 100644 index 000000000000..e396f2fd2822 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/functions/odasa6418.py @@ -0,0 +1,24 @@ + +from __future__ import print_function +import sys + +def bar(cond): + if cond: + fail("cond true") + + +def fail(message, *args): + print('Error:', message % args, file=sys.stderr) + sys.exit(1) + +def foo(cond): + bar() + +# To get the FP result reported in ODASA-6418, +#bar must be called directly (not transitively) from the module scope +bar(unknown()) + +#The following do not trigger the bug +#foo(unknown()) +#pass + diff --git a/python/ql/test/library-tests/PointsTo/functions/test.expected b/python/ql/test/library-tests/PointsTo/functions/test.expected new file mode 100644 index 000000000000..e29593725360 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/functions/test.expected @@ -0,0 +1,2 @@ +| 9 | Function meth | +| 14 | Function meth | \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/functions/test.py b/python/ql/test/library-tests/PointsTo/functions/test.py new file mode 100644 index 000000000000..b72d1f3bcd8a --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/functions/test.py @@ -0,0 +1,14 @@ +class Base(object): + + def meth(self): + pass + +class Derived1(Base): + + def uses_meth(self): + return self.meth() + +class Derived2(Derived1): + + def uses_meth(self): + return self.meth() diff --git a/python/ql/test/library-tests/PointsTo/functions/test.ql b/python/ql/test/library-tests/PointsTo/functions/test.ql new file mode 100644 index 000000000000..dd1a070d99f7 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/functions/test.ql @@ -0,0 +1,9 @@ +import python + +from Call c, FunctionObject f + +where c.getFunc().(Attribute).getObject().(Name).getId() = "self" +and +f.getACall().getNode() = c + +select c.getLocation().getStartLine(), f.toString() diff --git a/python/ql/test/library-tests/PointsTo/general/GlobalPointsTo.expected b/python/ql/test/library-tests/PointsTo/general/GlobalPointsTo.expected new file mode 100644 index 000000000000..f4a26e6919bd --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/general/GlobalPointsTo.expected @@ -0,0 +1,178 @@ +| Class Base | 147 | ControlFlowNode for FunctionExpr | Function __init__ | +| Class Base | 147 | ControlFlowNode for __init__ | Function __init__ | +| Class Base2 | 175 | ControlFlowNode for FunctionExpr | Function __init__ | +| Class Base2 | 175 | ControlFlowNode for __init__ | Function __init__ | +| Class Base2 | 178 | ControlFlowNode for IntegerLiteral | int 1 | +| Class Base2 | 178 | ControlFlowNode for x | int 1 | +| Class Derived4 | 182 | ControlFlowNode for FunctionExpr | Function __init__ | +| Class Derived4 | 182 | ControlFlowNode for __init__ | Function __init__ | +| Class E | 195 | ControlFlowNode for FunctionExpr | Function _internal | +| Class E | 195 | ControlFlowNode for _internal | Function _internal | +| Class E | 201 | ControlFlowNode for _internal | Function _internal | +| Class E | 201 | ControlFlowNode for _internal() | Function wrapper | +| Class E | 202 | ControlFlowNode for FunctionExpr | Function method | +| Class E | 202 | ControlFlowNode for method | Function wrapper | +| Class F | 250 | ControlFlowNode for g3 | NoneType None | +| Class F | 251 | ControlFlowNode for g3 | NoneType None | +| Class G | 256 | ControlFlowNode for IntegerLiteral | int 0 | +| Class G | 256 | ControlFlowNode for attr | int 0 | +| Class G | 258 | ControlFlowNode for FunctionExpr | Function __init__ | +| Class G | 258 | ControlFlowNode for __init__ | Function __init__ | +| Class G | 261 | ControlFlowNode for FunctionExpr | Function meth | +| Class G | 261 | ControlFlowNode for meth | Function meth | +| Class Ugly | 240 | ControlFlowNode for FunctionExpr | Function __init__ | +| Class Ugly | 240 | ControlFlowNode for __init__ | Function __init__ | +| Class Ugly | 244 | ControlFlowNode for FunctionExpr | Function meth | +| Class Ugly | 244 | ControlFlowNode for meth | Function meth | +| Class X | 36 | ControlFlowNode for classmethod | builtin-class classmethod | +| Class X | 36 | ControlFlowNode for classmethod() | classmethod() | +| Class X | 37 | ControlFlowNode for FunctionExpr | Function method1 | +| Class X | 37 | ControlFlowNode for method1 | classmethod() | +| Class X | 41 | ControlFlowNode for FunctionExpr | Function method2 | +| Module pointsto_test | 17 | ControlFlowNode for Attribute | list object | +| Module pointsto_test | 17 | ControlFlowNode for Compare | bool False | +| Module pointsto_test | 17 | ControlFlowNode for Compare | bool True | +| Module pointsto_test | 17 | ControlFlowNode for IntegerLiteral | int 2 | +| Module pointsto_test | 17 | ControlFlowNode for len | Builtin-function len | +| Module pointsto_test | 17 | ControlFlowNode for len() | len() | +| Module pointsto_test | 17 | ControlFlowNode for sys | Module sys | +| Module pointsto_test | 18 | ControlFlowNode for C | class C | +| Module pointsto_test | 18 | ControlFlowNode for v1 | class C | +| Module pointsto_test | 20 | ControlFlowNode for D | class D | +| Module pointsto_test | 20 | ControlFlowNode for v1 | class D | +| Module pointsto_test | 21 | ControlFlowNode for v1 | class C | +| Module pointsto_test | 21 | ControlFlowNode for v1 | class D | +| Module pointsto_test | 21 | ControlFlowNode for v1() | v1() | +| Module pointsto_test | 21 | ControlFlowNode for v2 | v1() | +| Module pointsto_test | 23 | ControlFlowNode for FunctionExpr | Function f | +| Module pointsto_test | 23 | ControlFlowNode for f | Function f | +| Module pointsto_test | 30 | ControlFlowNode for FunctionExpr | Function g | +| Module pointsto_test | 30 | ControlFlowNode for g | Function g | +| Module pointsto_test | 33 | ControlFlowNode for f | Function f | +| Module pointsto_test | 33 | ControlFlowNode for f() | C() | +| Module pointsto_test | 33 | ControlFlowNode for f() | D() | +| Module pointsto_test | 33 | ControlFlowNode for g | Function g | +| Module pointsto_test | 33 | ControlFlowNode for g() | C() | +| Module pointsto_test | 33 | ControlFlowNode for g() | D() | +| Module pointsto_test | 33 | ControlFlowNode for v4 | C() | +| Module pointsto_test | 33 | ControlFlowNode for v4 | D() | +| Module pointsto_test | 35 | ControlFlowNode for ClassExpr | class X | +| Module pointsto_test | 35 | ControlFlowNode for X | class X | +| Module pointsto_test | 35 | ControlFlowNode for object | builtin-class object | +| Module pointsto_test | 44 | ControlFlowNode for FunctionExpr | Function deco | +| Module pointsto_test | 44 | ControlFlowNode for deco | Function deco | +| Module pointsto_test | 47 | ControlFlowNode for v1 | class C | +| Module pointsto_test | 47 | ControlFlowNode for v1 | class D | +| Module pointsto_test | 48 | ControlFlowNode for v2 | v1() | +| Module pointsto_test | 50 | ControlFlowNode for v4 | C() | +| Module pointsto_test | 50 | ControlFlowNode for v4 | D() | +| Module pointsto_test | 51 | ControlFlowNode for list | builtin-class list | +| Module pointsto_test | 53 | ControlFlowNode for FunctionExpr | Function h | +| Module pointsto_test | 53 | ControlFlowNode for h | Function h | +| Module pointsto_test | 60 | ControlFlowNode for FunctionExpr | Function j | +| Module pointsto_test | 60 | ControlFlowNode for j | Function j | +| Module pointsto_test | 62 | ControlFlowNode for dict | builtin-class dict | +| Module pointsto_test | 63 | ControlFlowNode for IntegerLiteral | int 7 | +| Module pointsto_test | 63 | ControlFlowNode for dict | int 7 | +| Module pointsto_test | 64 | ControlFlowNode for dict | int 7 | +| Module pointsto_test | 65 | ControlFlowNode for tuple | builtin-class tuple | +| Module pointsto_test | 66 | ControlFlowNode for tuple | builtin-class tuple | +| Module pointsto_test | 69 | ControlFlowNode for X | class X | +| Module pointsto_test | 70 | ControlFlowNode for X | class X | +| Module pointsto_test | 72 | ControlFlowNode for ImportExpr | Module abc | +| Module pointsto_test | 72 | ControlFlowNode for ImportMember | Function abstractmethod | +| Module pointsto_test | 72 | ControlFlowNode for abstractmethod | Function abstractmethod | +| Module pointsto_test | 73 | ControlFlowNode for abstractmethod | Function abstractmethod | +| Module pointsto_test | 75 | ControlFlowNode for C | class C | +| Module pointsto_test | 75 | ControlFlowNode for C() | C() | +| Module pointsto_test | 75 | ControlFlowNode for type | builtin-class type | +| Module pointsto_test | 75 | ControlFlowNode for type() | class C | +| Module pointsto_test | 76 | ControlFlowNode for sys | Module sys | +| Module pointsto_test | 76 | ControlFlowNode for type | builtin-class type | +| Module pointsto_test | 76 | ControlFlowNode for type() | builtin-class module | +| Module pointsto_test | 78 | ControlFlowNode for type | builtin-class type | +| Module pointsto_test | 79 | ControlFlowNode for Dict | Dict | +| Module pointsto_test | 79 | ControlFlowNode for Tuple | Tuple | +| Module pointsto_test | 79 | ControlFlowNode for object | builtin-class object | +| Module pointsto_test | 79 | ControlFlowNode for type | builtin-class type | +| Module pointsto_test | 81 | ControlFlowNode for FunctionExpr | Function k | +| Module pointsto_test | 81 | ControlFlowNode for k | Function k | +| Module pointsto_test | 88 | ControlFlowNode for FunctionExpr | Function outer | +| Module pointsto_test | 88 | ControlFlowNode for outer | Function outer | +| Module pointsto_test | 95 | ControlFlowNode for FunctionExpr | Function never_none | +| Module pointsto_test | 95 | ControlFlowNode for never_none | Function never_none | +| Module pointsto_test | 104 | ControlFlowNode for FunctionExpr | Function outer_use_vars | +| Module pointsto_test | 104 | ControlFlowNode for outer_use_vars | Function outer_use_vars | +| Module pointsto_test | 112 | ControlFlowNode for FunctionExpr | Function literals_in_func | +| Module pointsto_test | 112 | ControlFlowNode for literals_in_func | Function literals_in_func | +| Module pointsto_test | 122 | ControlFlowNode for Lambda | Function lambda | +| Module pointsto_test | 122 | ControlFlowNode for y | Function lambda | +| Module pointsto_test | 124 | ControlFlowNode for FunctionExpr | Function following | +| Module pointsto_test | 124 | ControlFlowNode for following | Function following | +| Module pointsto_test | 127 | ControlFlowNode for Dict | Dict | +| Module pointsto_test | 127 | ControlFlowNode for FunctionExpr | Function params_and_defaults | +| Module pointsto_test | 127 | ControlFlowNode for IntegerLiteral | int 1 | +| Module pointsto_test | 127 | ControlFlowNode for params_and_defaults | Function params_and_defaults | +| Module pointsto_test | 132 | ControlFlowNode for FunctionExpr | Function inner_cls | +| Module pointsto_test | 132 | ControlFlowNode for inner_cls | Function inner_cls | +| Module pointsto_test | 138 | ControlFlowNode for ImportExpr | Module xyz | +| Module pointsto_test | 139 | ControlFlowNode for ImportExpr | Module xyz | +| Module pointsto_test | 139 | ControlFlowNode for xyz | Module xyz | +| Module pointsto_test | 140 | ControlFlowNode for Attribute | float 1.0 | +| Module pointsto_test | 140 | ControlFlowNode for xyz | Module xyz | +| Module pointsto_test | 141 | ControlFlowNode for z | float 3.0 | +| Module pointsto_test | 145 | ControlFlowNode for Base | class Base | +| Module pointsto_test | 145 | ControlFlowNode for ClassExpr | class Base | +| Module pointsto_test | 145 | ControlFlowNode for object | builtin-class object | +| Module pointsto_test | 155 | ControlFlowNode for Base | class Base | +| Module pointsto_test | 155 | ControlFlowNode for ClassExpr | class Derived1 | +| Module pointsto_test | 155 | ControlFlowNode for Derived1 | class Derived1 | +| Module pointsto_test | 158 | ControlFlowNode for Base | class Base | +| Module pointsto_test | 158 | ControlFlowNode for ClassExpr | class Derived2 | +| Module pointsto_test | 158 | ControlFlowNode for Derived2 | class Derived2 | +| Module pointsto_test | 161 | ControlFlowNode for Base | class Base | +| Module pointsto_test | 161 | ControlFlowNode for ClassExpr | class Derived3 | +| Module pointsto_test | 161 | ControlFlowNode for Derived3 | class Derived3 | +| Module pointsto_test | 164 | ControlFlowNode for Base | class Base | +| Module pointsto_test | 167 | ControlFlowNode for FunctionExpr | Function multiple_assignment | +| Module pointsto_test | 167 | ControlFlowNode for multiple_assignment | Function multiple_assignment | +| Module pointsto_test | 173 | ControlFlowNode for Base2 | class Base2 | +| Module pointsto_test | 173 | ControlFlowNode for ClassExpr | class Base2 | +| Module pointsto_test | 173 | ControlFlowNode for object | builtin-class object | +| Module pointsto_test | 180 | ControlFlowNode for Base2 | class Base2 | +| Module pointsto_test | 180 | ControlFlowNode for ClassExpr | class Derived4 | +| Module pointsto_test | 180 | ControlFlowNode for Derived4 | class Derived4 | +| Module pointsto_test | 187 | ControlFlowNode for FunctionExpr | Function vararg_kwarg | +| Module pointsto_test | 187 | ControlFlowNode for vararg_kwarg | Function vararg_kwarg | +| Module pointsto_test | 193 | ControlFlowNode for ClassExpr | class E | +| Module pointsto_test | 193 | ControlFlowNode for E | class E | +| Module pointsto_test | 193 | ControlFlowNode for object | builtin-class object | +| Module pointsto_test | 206 | ControlFlowNode for FunctionExpr | Function calls_next | +| Module pointsto_test | 206 | ControlFlowNode for calls_next | Function calls_next | +| Module pointsto_test | 213 | ControlFlowNode for ImportExpr | Module sys | +| Module pointsto_test | 213 | ControlFlowNode for ImportMember | Builtin-function exit | +| Module pointsto_test | 213 | ControlFlowNode for exit | Builtin-function exit | +| Module pointsto_test | 217 | ControlFlowNode for None | NoneType None | +| Module pointsto_test | 217 | ControlFlowNode for g1 | NoneType None | +| Module pointsto_test | 219 | ControlFlowNode for FunctionExpr | Function assign_global | +| Module pointsto_test | 219 | ControlFlowNode for assign_global | Function assign_global | +| Module pointsto_test | 226 | ControlFlowNode for None | NoneType None | +| Module pointsto_test | 226 | ControlFlowNode for g2 | NoneType None | +| Module pointsto_test | 228 | ControlFlowNode for FunctionExpr | Function init | +| Module pointsto_test | 228 | ControlFlowNode for init | Function init | +| Module pointsto_test | 232 | ControlFlowNode for init | Function init | +| Module pointsto_test | 232 | ControlFlowNode for init() | NoneType None | +| Module pointsto_test | 233 | ControlFlowNode for g2 | int 102 | +| Module pointsto_test | 236 | ControlFlowNode for None | NoneType None | +| Module pointsto_test | 236 | ControlFlowNode for g3 | NoneType None | +| Module pointsto_test | 238 | ControlFlowNode for ClassExpr | class Ugly | +| Module pointsto_test | 238 | ControlFlowNode for Ugly | class Ugly | +| Module pointsto_test | 238 | ControlFlowNode for object | builtin-class object | +| Module pointsto_test | 248 | ControlFlowNode for ClassExpr | class F | +| Module pointsto_test | 248 | ControlFlowNode for F | class F | +| Module pointsto_test | 248 | ControlFlowNode for object | builtin-class object | +| Module pointsto_test | 254 | ControlFlowNode for ClassExpr | class G | +| Module pointsto_test | 254 | ControlFlowNode for G | class G | +| Module pointsto_test | 254 | ControlFlowNode for object | builtin-class object | +| Module pointsto_test | 267 | ControlFlowNode for Derived4 | class Derived4 | +| Module pointsto_test | 267 | ControlFlowNode for Derived4() | Derived4() | diff --git a/python/ql/test/library-tests/PointsTo/general/GlobalPointsTo.ql b/python/ql/test/library-tests/PointsTo/general/GlobalPointsTo.ql new file mode 100644 index 000000000000..147b7835e244 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/general/GlobalPointsTo.ql @@ -0,0 +1,10 @@ + +import python +import interesting + +from int line, ControlFlowNode f, Object o, ImportTimeScope n +where +of_interest(f, line) and +f.refersTo(o) and +f.getScope() = n +select n.toString(), line, f.toString(), o.toString() diff --git a/python/ql/test/library-tests/PointsTo/general/LocalPointsTo.expected b/python/ql/test/library-tests/PointsTo/general/LocalPointsTo.expected new file mode 100644 index 000000000000..7f1325561801 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/general/LocalPointsTo.expected @@ -0,0 +1,341 @@ +| 17 | ControlFlowNode for Attribute | list object | +| 17 | ControlFlowNode for Compare | bool False | +| 17 | ControlFlowNode for Compare | bool True | +| 17 | ControlFlowNode for IntegerLiteral | int 2 | +| 17 | ControlFlowNode for len | Builtin-function len | +| 17 | ControlFlowNode for len() | len() | +| 17 | ControlFlowNode for sys | Module sys | +| 18 | ControlFlowNode for C | class C | +| 18 | ControlFlowNode for v1 | class C | +| 20 | ControlFlowNode for D | class D | +| 20 | ControlFlowNode for v1 | class D | +| 21 | ControlFlowNode for v1 | class C | +| 21 | ControlFlowNode for v1 | class D | +| 21 | ControlFlowNode for v1() | v1() | +| 21 | ControlFlowNode for v2 | v1() | +| 23 | ControlFlowNode for FunctionExpr | Function f | +| 23 | ControlFlowNode for f | Function f | +| 24 | ControlFlowNode for Attribute | list object | +| 24 | ControlFlowNode for Compare | bool False | +| 24 | ControlFlowNode for Compare | bool True | +| 24 | ControlFlowNode for IntegerLiteral | int 3 | +| 24 | ControlFlowNode for len | Builtin-function len | +| 24 | ControlFlowNode for len() | len() | +| 24 | ControlFlowNode for sys | Module sys | +| 25 | ControlFlowNode for C | class C | +| 25 | ControlFlowNode for C() | C() | +| 25 | ControlFlowNode for v3 | C() | +| 27 | ControlFlowNode for D | class D | +| 27 | ControlFlowNode for D() | D() | +| 27 | ControlFlowNode for v3 | D() | +| 28 | ControlFlowNode for v3 | C() | +| 28 | ControlFlowNode for v3 | D() | +| 30 | ControlFlowNode for FunctionExpr | Function g | +| 30 | ControlFlowNode for g | Function g | +| 31 | ControlFlowNode for arg | C() | +| 31 | ControlFlowNode for arg | D() | +| 33 | ControlFlowNode for f | Function f | +| 33 | ControlFlowNode for f() | C() | +| 33 | ControlFlowNode for f() | D() | +| 33 | ControlFlowNode for g | Function g | +| 33 | ControlFlowNode for g() | C() | +| 33 | ControlFlowNode for g() | D() | +| 33 | ControlFlowNode for v4 | C() | +| 33 | ControlFlowNode for v4 | D() | +| 35 | ControlFlowNode for ClassExpr | class X | +| 35 | ControlFlowNode for X | class X | +| 35 | ControlFlowNode for object | builtin-class object | +| 36 | ControlFlowNode for classmethod | builtin-class classmethod | +| 36 | ControlFlowNode for classmethod() | classmethod() | +| 37 | ControlFlowNode for FunctionExpr | Function method1 | +| 37 | ControlFlowNode for method1 | classmethod() | +| 41 | ControlFlowNode for FunctionExpr | Function method2 | +| 44 | ControlFlowNode for FunctionExpr | Function deco | +| 44 | ControlFlowNode for deco | Function deco | +| 47 | ControlFlowNode for v1 | class C | +| 47 | ControlFlowNode for v1 | class D | +| 48 | ControlFlowNode for v2 | v1() | +| 50 | ControlFlowNode for v4 | C() | +| 50 | ControlFlowNode for v4 | D() | +| 51 | ControlFlowNode for list | builtin-class list | +| 53 | ControlFlowNode for FunctionExpr | Function h | +| 53 | ControlFlowNode for h | Function h | +| 54 | ControlFlowNode for Attribute | list object | +| 54 | ControlFlowNode for Compare | bool False | +| 54 | ControlFlowNode for Compare | bool True | +| 54 | ControlFlowNode for IntegerLiteral | int 4 | +| 54 | ControlFlowNode for len | Builtin-function len | +| 54 | ControlFlowNode for len() | len() | +| 54 | ControlFlowNode for sys | Module sys | +| 55 | ControlFlowNode for C | class C | +| 55 | ControlFlowNode for C() | C() | +| 55 | ControlFlowNode for v5 | C() | +| 57 | ControlFlowNode for D | class D | +| 57 | ControlFlowNode for D() | D() | +| 57 | ControlFlowNode for v5 | D() | +| 58 | ControlFlowNode for Tuple | Tuple | +| 58 | ControlFlowNode for list | builtin-class list | +| 58 | ControlFlowNode for list() | list() | +| 58 | ControlFlowNode for v5 | C() | +| 58 | ControlFlowNode for v5 | D() | +| 60 | ControlFlowNode for FunctionExpr | Function j | +| 60 | ControlFlowNode for j | Function j | +| 61 | ControlFlowNode for Tuple | Tuple | +| 61 | ControlFlowNode for dict | int 7 | +| 61 | ControlFlowNode for tuple | builtin-class tuple | +| 62 | ControlFlowNode for dict | builtin-class dict | +| 63 | ControlFlowNode for IntegerLiteral | int 7 | +| 63 | ControlFlowNode for dict | int 7 | +| 64 | ControlFlowNode for dict | int 7 | +| 65 | ControlFlowNode for tuple | builtin-class tuple | +| 66 | ControlFlowNode for tuple | builtin-class tuple | +| 69 | ControlFlowNode for X | class X | +| 70 | ControlFlowNode for X | class X | +| 72 | ControlFlowNode for ImportExpr | Module abc | +| 72 | ControlFlowNode for ImportMember | Function abstractmethod | +| 72 | ControlFlowNode for abstractmethod | Function abstractmethod | +| 73 | ControlFlowNode for abstractmethod | Function abstractmethod | +| 75 | ControlFlowNode for C | class C | +| 75 | ControlFlowNode for C() | C() | +| 75 | ControlFlowNode for type | builtin-class type | +| 75 | ControlFlowNode for type() | class C | +| 76 | ControlFlowNode for sys | Module sys | +| 76 | ControlFlowNode for type | builtin-class type | +| 76 | ControlFlowNode for type() | builtin-class module | +| 78 | ControlFlowNode for type | builtin-class type | +| 79 | ControlFlowNode for Dict | Dict | +| 79 | ControlFlowNode for Tuple | Tuple | +| 79 | ControlFlowNode for object | builtin-class object | +| 79 | ControlFlowNode for type | builtin-class type | +| 81 | ControlFlowNode for FunctionExpr | Function k | +| 81 | ControlFlowNode for k | Function k | +| 82 | ControlFlowNode for C | class C | +| 82 | ControlFlowNode for C() | C() | +| 82 | ControlFlowNode for type | builtin-class type | +| 82 | ControlFlowNode for type() | class C | +| 83 | ControlFlowNode for sys | Module sys | +| 83 | ControlFlowNode for type | builtin-class type | +| 83 | ControlFlowNode for type() | builtin-class module | +| 84 | ControlFlowNode for type | builtin-class type | +| 85 | ControlFlowNode for Dict | Dict | +| 85 | ControlFlowNode for Tuple | Tuple | +| 85 | ControlFlowNode for object | builtin-class object | +| 85 | ControlFlowNode for type | builtin-class type | +| 88 | ControlFlowNode for FunctionExpr | Function outer | +| 88 | ControlFlowNode for outer | Function outer | +| 89 | ControlFlowNode for IntegerLiteral | int 1 | +| 89 | ControlFlowNode for y | int 1 | +| 90 | ControlFlowNode for FunctionExpr | Function inner | +| 90 | ControlFlowNode for inner | Function inner | +| 92 | ControlFlowNode for IntegerLiteral | int 2 | +| 92 | ControlFlowNode for z | int 2 | +| 93 | ControlFlowNode for inner | Function inner | +| 95 | ControlFlowNode for FunctionExpr | Function never_none | +| 95 | ControlFlowNode for never_none | Function never_none | +| 97 | ControlFlowNode for FloatLiteral | float 1.0 | +| 97 | ControlFlowNode for y | float 1.0 | +| 99 | ControlFlowNode for None | NoneType None | +| 99 | ControlFlowNode for y | NoneType None | +| 100 | ControlFlowNode for Compare | bool False | +| 100 | ControlFlowNode for Compare | bool True | +| 100 | ControlFlowNode for None | NoneType None | +| 100 | ControlFlowNode for y | NoneType None | +| 100 | ControlFlowNode for y | float 1.0 | +| 101 | ControlFlowNode for FloatLiteral | float 0.0 | +| 101 | ControlFlowNode for y | float 0.0 | +| 102 | ControlFlowNode for y | float 0.0 | +| 102 | ControlFlowNode for y | float 1.0 | +| 104 | ControlFlowNode for FunctionExpr | Function outer_use_vars | +| 104 | ControlFlowNode for outer_use_vars | Function outer_use_vars | +| 105 | ControlFlowNode for IntegerLiteral | int 1 | +| 105 | ControlFlowNode for y | int 1 | +| 106 | ControlFlowNode for FunctionExpr | Function inner | +| 106 | ControlFlowNode for inner | Function inner | +| 108 | ControlFlowNode for IntegerLiteral | int 2 | +| 108 | ControlFlowNode for z | int 2 | +| 109 | ControlFlowNode for y | int 1 | +| 109 | ControlFlowNode for z | int 2 | +| 110 | ControlFlowNode for inner | Function inner | +| 112 | ControlFlowNode for FunctionExpr | Function literals_in_func | +| 112 | ControlFlowNode for literals_in_func | Function literals_in_func | +| 113 | ControlFlowNode for True | bool True | +| 114 | ControlFlowNode for None | NoneType None | +| 115 | ControlFlowNode for IntegerLiteral | int 1346 | +| 116 | ControlFlowNode for FloatLiteral | float 0.7 | +| 117 | ControlFlowNode for ClassExpr | class X | +| 117 | ControlFlowNode for X | class X | +| 117 | ControlFlowNode for object | builtin-class object | +| 118 | ControlFlowNode for FunctionExpr | Function f | +| 118 | ControlFlowNode for f | Function f | +| 119 | ControlFlowNode for Tuple | Tuple | +| 120 | ControlFlowNode for List | List | +| 122 | ControlFlowNode for Lambda | Function lambda | +| 122 | ControlFlowNode for following | Function following | +| 122 | ControlFlowNode for following() | NoneType None | +| 122 | ControlFlowNode for y | Function lambda | +| 124 | ControlFlowNode for FunctionExpr | Function following | +| 124 | ControlFlowNode for following | Function following | +| 127 | ControlFlowNode for Dict | Dict | +| 127 | ControlFlowNode for FunctionExpr | Function params_and_defaults | +| 127 | ControlFlowNode for IntegerLiteral | int 1 | +| 127 | ControlFlowNode for params_and_defaults | Function params_and_defaults | +| 129 | ControlFlowNode for b | Dict | +| 130 | ControlFlowNode for c | int 1 | +| 132 | ControlFlowNode for FunctionExpr | Function inner_cls | +| 132 | ControlFlowNode for inner_cls | Function inner_cls | +| 133 | ControlFlowNode for A | class A | +| 133 | ControlFlowNode for BaseException | builtin-class BaseException | +| 133 | ControlFlowNode for ClassExpr | class A | +| 135 | ControlFlowNode for A | class A | +| 135 | ControlFlowNode for A() | A() | +| 135 | ControlFlowNode for a | A() | +| 136 | ControlFlowNode for a | A() | +| 138 | ControlFlowNode for ImportExpr | Module xyz | +| 139 | ControlFlowNode for ImportExpr | Module xyz | +| 139 | ControlFlowNode for xyz | Module xyz | +| 140 | ControlFlowNode for Attribute | float 1.0 | +| 140 | ControlFlowNode for xyz | Module xyz | +| 141 | ControlFlowNode for z | float 3.0 | +| 145 | ControlFlowNode for Base | class Base | +| 145 | ControlFlowNode for ClassExpr | class Base | +| 145 | ControlFlowNode for object | builtin-class object | +| 147 | ControlFlowNode for FunctionExpr | Function __init__ | +| 147 | ControlFlowNode for __init__ | Function __init__ | +| 148 | ControlFlowNode for Compare | bool False | +| 148 | ControlFlowNode for Compare | bool True | +| 148 | ControlFlowNode for IntegerLiteral | int 1 | +| 149 | ControlFlowNode for Attribute | class Derived1 | +| 149 | ControlFlowNode for Derived1 | class Derived1 | +| 149 | ControlFlowNode for self | self | +| 150 | ControlFlowNode for Compare | bool False | +| 150 | ControlFlowNode for Compare | bool True | +| 150 | ControlFlowNode for IntegerLiteral | int 2 | +| 151 | ControlFlowNode for Attribute | class Derived2 | +| 151 | ControlFlowNode for Derived2 | class Derived2 | +| 151 | ControlFlowNode for self | self | +| 153 | ControlFlowNode for Attribute | class Derived3 | +| 153 | ControlFlowNode for Derived3 | class Derived3 | +| 153 | ControlFlowNode for self | self | +| 155 | ControlFlowNode for Base | class Base | +| 155 | ControlFlowNode for ClassExpr | class Derived1 | +| 155 | ControlFlowNode for Derived1 | class Derived1 | +| 158 | ControlFlowNode for Base | class Base | +| 158 | ControlFlowNode for ClassExpr | class Derived2 | +| 158 | ControlFlowNode for Derived2 | class Derived2 | +| 161 | ControlFlowNode for Base | class Base | +| 161 | ControlFlowNode for ClassExpr | class Derived3 | +| 161 | ControlFlowNode for Derived3 | class Derived3 | +| 164 | ControlFlowNode for Base | class Base | +| 167 | ControlFlowNode for FunctionExpr | Function multiple_assignment | +| 167 | ControlFlowNode for multiple_assignment | Function multiple_assignment | +| 168 | ControlFlowNode for Tuple | Tuple | +| 168 | ControlFlowNode for _list | builtin-class list | +| 168 | ControlFlowNode for _tuple | builtin-class tuple | +| 168 | ControlFlowNode for list | builtin-class list | +| 168 | ControlFlowNode for tuple | builtin-class tuple | +| 169 | ControlFlowNode for _tuple | builtin-class tuple | +| 170 | ControlFlowNode for _list | builtin-class list | +| 173 | ControlFlowNode for Base2 | class Base2 | +| 173 | ControlFlowNode for ClassExpr | class Base2 | +| 173 | ControlFlowNode for object | builtin-class object | +| 175 | ControlFlowNode for FunctionExpr | Function __init__ | +| 175 | ControlFlowNode for __init__ | Function __init__ | +| 178 | ControlFlowNode for IntegerLiteral | int 1 | +| 178 | ControlFlowNode for x | int 1 | +| 180 | ControlFlowNode for Base2 | class Base2 | +| 180 | ControlFlowNode for ClassExpr | class Derived4 | +| 180 | ControlFlowNode for Derived4 | class Derived4 | +| 182 | ControlFlowNode for FunctionExpr | Function __init__ | +| 182 | ControlFlowNode for __init__ | Function __init__ | +| 183 | ControlFlowNode for Attribute | super().x | +| 183 | ControlFlowNode for Derived4 | class Derived4 | +| 183 | ControlFlowNode for self | self | +| 183 | ControlFlowNode for super | builtin-class super | +| 183 | ControlFlowNode for super() | super() | +| 184 | ControlFlowNode for Attribute | super().__init__ | +| 184 | ControlFlowNode for Attribute() | NoneType None | +| 184 | ControlFlowNode for Derived4 | class Derived4 | +| 184 | ControlFlowNode for self | self | +| 184 | ControlFlowNode for super | builtin-class super | +| 184 | ControlFlowNode for super() | super() | +| 187 | ControlFlowNode for FunctionExpr | Function vararg_kwarg | +| 187 | ControlFlowNode for d | d | +| 187 | ControlFlowNode for t | t | +| 187 | ControlFlowNode for vararg_kwarg | Function vararg_kwarg | +| 188 | ControlFlowNode for t | t | +| 189 | ControlFlowNode for d | d | +| 193 | ControlFlowNode for ClassExpr | class E | +| 193 | ControlFlowNode for E | class E | +| 193 | ControlFlowNode for object | builtin-class object | +| 195 | ControlFlowNode for FunctionExpr | Function _internal | +| 195 | ControlFlowNode for _internal | Function _internal | +| 197 | ControlFlowNode for FunctionExpr | Function wrapper | +| 197 | ControlFlowNode for wrapper | Function wrapper | +| 199 | ControlFlowNode for wrapper | Function wrapper | +| 201 | ControlFlowNode for _internal | Function _internal | +| 201 | ControlFlowNode for _internal() | Function wrapper | +| 202 | ControlFlowNode for FunctionExpr | Function method | +| 202 | ControlFlowNode for args | args | +| 202 | ControlFlowNode for method | Function wrapper | +| 206 | ControlFlowNode for FunctionExpr | Function calls_next | +| 206 | ControlFlowNode for calls_next | Function calls_next | +| 207 | ControlFlowNode for iter | Builtin-function iter | +| 208 | ControlFlowNode for next | Builtin-function next | +| 213 | ControlFlowNode for ImportExpr | Module sys | +| 213 | ControlFlowNode for ImportMember | Builtin-function exit | +| 213 | ControlFlowNode for exit | Builtin-function exit | +| 217 | ControlFlowNode for None | NoneType None | +| 217 | ControlFlowNode for g1 | NoneType None | +| 219 | ControlFlowNode for FunctionExpr | Function assign_global | +| 219 | ControlFlowNode for assign_global | Function assign_global | +| 221 | ControlFlowNode for IntegerLiteral | int 101 | +| 221 | ControlFlowNode for g1 | int 101 | +| 222 | ControlFlowNode for g1 | int 101 | +| 226 | ControlFlowNode for None | NoneType None | +| 226 | ControlFlowNode for g2 | NoneType None | +| 228 | ControlFlowNode for FunctionExpr | Function init | +| 228 | ControlFlowNode for init | Function init | +| 230 | ControlFlowNode for IntegerLiteral | int 102 | +| 230 | ControlFlowNode for g2 | int 102 | +| 232 | ControlFlowNode for init | Function init | +| 232 | ControlFlowNode for init() | NoneType None | +| 233 | ControlFlowNode for g2 | int 102 | +| 236 | ControlFlowNode for None | NoneType None | +| 236 | ControlFlowNode for g3 | NoneType None | +| 238 | ControlFlowNode for ClassExpr | class Ugly | +| 238 | ControlFlowNode for Ugly | class Ugly | +| 238 | ControlFlowNode for object | builtin-class object | +| 240 | ControlFlowNode for FunctionExpr | Function __init__ | +| 240 | ControlFlowNode for __init__ | Function __init__ | +| 242 | ControlFlowNode for IntegerLiteral | int 103 | +| 242 | ControlFlowNode for g3 | int 103 | +| 244 | ControlFlowNode for FunctionExpr | Function meth | +| 244 | ControlFlowNode for meth | Function meth | +| 245 | ControlFlowNode for g3 | int 103 | +| 248 | ControlFlowNode for ClassExpr | class F | +| 248 | ControlFlowNode for F | class F | +| 248 | ControlFlowNode for object | builtin-class object | +| 250 | ControlFlowNode for g3 | NoneType None | +| 251 | ControlFlowNode for g3 | NoneType None | +| 254 | ControlFlowNode for ClassExpr | class G | +| 254 | ControlFlowNode for G | class G | +| 254 | ControlFlowNode for object | builtin-class object | +| 256 | ControlFlowNode for IntegerLiteral | int 0 | +| 256 | ControlFlowNode for attr | int 0 | +| 258 | ControlFlowNode for FunctionExpr | Function __init__ | +| 258 | ControlFlowNode for __init__ | Function __init__ | +| 259 | ControlFlowNode for Attribute | int 1 | +| 259 | ControlFlowNode for IntegerLiteral | int 1 | +| 259 | ControlFlowNode for self | self | +| 261 | ControlFlowNode for FunctionExpr | Function meth | +| 261 | ControlFlowNode for meth | Function meth | +| 262 | ControlFlowNode for Attribute | int 2 | +| 262 | ControlFlowNode for IntegerLiteral | int 2 | +| 262 | ControlFlowNode for self | self | +| 263 | ControlFlowNode for Attribute | int 3 | +| 263 | ControlFlowNode for IntegerLiteral | int 3 | +| 263 | ControlFlowNode for self | self | +| 264 | ControlFlowNode for Attribute | int 3 | +| 264 | ControlFlowNode for self | self | +| 267 | ControlFlowNode for Derived4 | class Derived4 | +| 267 | ControlFlowNode for Derived4() | Derived4() | diff --git a/python/ql/test/library-tests/PointsTo/general/LocalPointsTo.ql b/python/ql/test/library-tests/PointsTo/general/LocalPointsTo.ql new file mode 100644 index 000000000000..a3e5d40486dd --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/general/LocalPointsTo.ql @@ -0,0 +1,16 @@ +/** + * @name LocalPointsTo + * @description Insert description here... + * @kind problem + * @problem.severity warning + */ + +import python +import interesting +import Util + +from int line, ControlFlowNode f, Object o +where + of_interest(f, line) and + f.refersTo(o) +select line, f.toString(), repr(o) diff --git a/python/ql/test/library-tests/PointsTo/general/LocalPointsToType.expected b/python/ql/test/library-tests/PointsTo/general/LocalPointsToType.expected new file mode 100644 index 000000000000..2f1d347892d6 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/general/LocalPointsToType.expected @@ -0,0 +1,344 @@ +| 17 | ControlFlowNode for Attribute | list object | builtin-class list | +| 17 | ControlFlowNode for Compare | bool False | builtin-class bool | +| 17 | ControlFlowNode for Compare | bool True | builtin-class bool | +| 17 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | +| 17 | ControlFlowNode for len | Builtin-function len | builtin-class builtin_function_or_method | +| 17 | ControlFlowNode for len() | len() | builtin-class int | +| 17 | ControlFlowNode for sys | Module sys | builtin-class module | +| 18 | ControlFlowNode for C | class C | builtin-class type | +| 18 | ControlFlowNode for v1 | class C | builtin-class type | +| 20 | ControlFlowNode for D | class D | builtin-class type | +| 20 | ControlFlowNode for v1 | class D | builtin-class type | +| 21 | ControlFlowNode for v1 | class C | builtin-class type | +| 21 | ControlFlowNode for v1 | class D | builtin-class type | +| 21 | ControlFlowNode for v1() | v1() | class C | +| 21 | ControlFlowNode for v1() | v1() | class D | +| 21 | ControlFlowNode for v2 | v1() | class C | +| 21 | ControlFlowNode for v2 | v1() | class D | +| 23 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | +| 23 | ControlFlowNode for f | Function f | builtin-class function | +| 24 | ControlFlowNode for Attribute | list object | builtin-class list | +| 24 | ControlFlowNode for Compare | bool False | builtin-class bool | +| 24 | ControlFlowNode for Compare | bool True | builtin-class bool | +| 24 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | +| 24 | ControlFlowNode for len | Builtin-function len | builtin-class builtin_function_or_method | +| 24 | ControlFlowNode for len() | len() | builtin-class int | +| 24 | ControlFlowNode for sys | Module sys | builtin-class module | +| 25 | ControlFlowNode for C | class C | builtin-class type | +| 25 | ControlFlowNode for C() | C() | class C | +| 25 | ControlFlowNode for v3 | C() | class C | +| 27 | ControlFlowNode for D | class D | builtin-class type | +| 27 | ControlFlowNode for D() | D() | class D | +| 27 | ControlFlowNode for v3 | D() | class D | +| 28 | ControlFlowNode for v3 | C() | class C | +| 28 | ControlFlowNode for v3 | D() | class D | +| 30 | ControlFlowNode for FunctionExpr | Function g | builtin-class function | +| 30 | ControlFlowNode for g | Function g | builtin-class function | +| 31 | ControlFlowNode for arg | C() | class C | +| 31 | ControlFlowNode for arg | D() | class D | +| 33 | ControlFlowNode for f | Function f | builtin-class function | +| 33 | ControlFlowNode for f() | C() | class C | +| 33 | ControlFlowNode for f() | D() | class D | +| 33 | ControlFlowNode for g | Function g | builtin-class function | +| 33 | ControlFlowNode for g() | C() | class C | +| 33 | ControlFlowNode for g() | D() | class D | +| 33 | ControlFlowNode for v4 | C() | class C | +| 33 | ControlFlowNode for v4 | D() | class D | +| 35 | ControlFlowNode for ClassExpr | class X | builtin-class type | +| 35 | ControlFlowNode for X | class X | builtin-class type | +| 35 | ControlFlowNode for object | builtin-class object | builtin-class type | +| 36 | ControlFlowNode for classmethod | builtin-class classmethod | builtin-class type | +| 36 | ControlFlowNode for classmethod() | classmethod() | builtin-class classmethod | +| 37 | ControlFlowNode for FunctionExpr | Function method1 | builtin-class function | +| 37 | ControlFlowNode for method1 | classmethod() | builtin-class classmethod | +| 41 | ControlFlowNode for FunctionExpr | Function method2 | builtin-class function | +| 44 | ControlFlowNode for FunctionExpr | Function deco | builtin-class function | +| 44 | ControlFlowNode for deco | Function deco | builtin-class function | +| 47 | ControlFlowNode for v1 | class C | builtin-class type | +| 47 | ControlFlowNode for v1 | class D | builtin-class type | +| 48 | ControlFlowNode for v2 | v1() | class C | +| 48 | ControlFlowNode for v2 | v1() | class D | +| 50 | ControlFlowNode for v4 | C() | class C | +| 50 | ControlFlowNode for v4 | D() | class D | +| 51 | ControlFlowNode for list | builtin-class list | builtin-class type | +| 53 | ControlFlowNode for FunctionExpr | Function h | builtin-class function | +| 53 | ControlFlowNode for h | Function h | builtin-class function | +| 54 | ControlFlowNode for Attribute | list object | builtin-class list | +| 54 | ControlFlowNode for Compare | bool False | builtin-class bool | +| 54 | ControlFlowNode for Compare | bool True | builtin-class bool | +| 54 | ControlFlowNode for IntegerLiteral | int 4 | builtin-class int | +| 54 | ControlFlowNode for len | Builtin-function len | builtin-class builtin_function_or_method | +| 54 | ControlFlowNode for len() | len() | builtin-class int | +| 54 | ControlFlowNode for sys | Module sys | builtin-class module | +| 55 | ControlFlowNode for C | class C | builtin-class type | +| 55 | ControlFlowNode for C() | C() | class C | +| 55 | ControlFlowNode for v5 | C() | class C | +| 57 | ControlFlowNode for D | class D | builtin-class type | +| 57 | ControlFlowNode for D() | D() | class D | +| 57 | ControlFlowNode for v5 | D() | class D | +| 58 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | +| 58 | ControlFlowNode for list | builtin-class list | builtin-class type | +| 58 | ControlFlowNode for list() | list() | builtin-class list | +| 58 | ControlFlowNode for v5 | C() | class C | +| 58 | ControlFlowNode for v5 | D() | class D | +| 60 | ControlFlowNode for FunctionExpr | Function j | builtin-class function | +| 60 | ControlFlowNode for j | Function j | builtin-class function | +| 61 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | +| 61 | ControlFlowNode for dict | int 7 | builtin-class int | +| 61 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type | +| 62 | ControlFlowNode for dict | builtin-class dict | builtin-class type | +| 63 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | +| 63 | ControlFlowNode for dict | int 7 | builtin-class int | +| 64 | ControlFlowNode for dict | int 7 | builtin-class int | +| 65 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type | +| 66 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type | +| 69 | ControlFlowNode for X | class X | builtin-class type | +| 70 | ControlFlowNode for X | class X | builtin-class type | +| 72 | ControlFlowNode for ImportExpr | Module abc | builtin-class module | +| 72 | ControlFlowNode for ImportMember | Function abstractmethod | builtin-class function | +| 72 | ControlFlowNode for abstractmethod | Function abstractmethod | builtin-class function | +| 73 | ControlFlowNode for abstractmethod | Function abstractmethod | builtin-class function | +| 75 | ControlFlowNode for C | class C | builtin-class type | +| 75 | ControlFlowNode for C() | C() | class C | +| 75 | ControlFlowNode for type | builtin-class type | builtin-class type | +| 75 | ControlFlowNode for type() | class C | builtin-class type | +| 76 | ControlFlowNode for sys | Module sys | builtin-class module | +| 76 | ControlFlowNode for type | builtin-class type | builtin-class type | +| 76 | ControlFlowNode for type() | builtin-class module | builtin-class type | +| 78 | ControlFlowNode for type | builtin-class type | builtin-class type | +| 79 | ControlFlowNode for Dict | Dict | builtin-class dict | +| 79 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | +| 79 | ControlFlowNode for object | builtin-class object | builtin-class type | +| 79 | ControlFlowNode for type | builtin-class type | builtin-class type | +| 81 | ControlFlowNode for FunctionExpr | Function k | builtin-class function | +| 81 | ControlFlowNode for k | Function k | builtin-class function | +| 82 | ControlFlowNode for C | class C | builtin-class type | +| 82 | ControlFlowNode for C() | C() | class C | +| 82 | ControlFlowNode for type | builtin-class type | builtin-class type | +| 82 | ControlFlowNode for type() | class C | builtin-class type | +| 83 | ControlFlowNode for sys | Module sys | builtin-class module | +| 83 | ControlFlowNode for type | builtin-class type | builtin-class type | +| 83 | ControlFlowNode for type() | builtin-class module | builtin-class type | +| 84 | ControlFlowNode for type | builtin-class type | builtin-class type | +| 85 | ControlFlowNode for Dict | Dict | builtin-class dict | +| 85 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | +| 85 | ControlFlowNode for object | builtin-class object | builtin-class type | +| 85 | ControlFlowNode for type | builtin-class type | builtin-class type | +| 88 | ControlFlowNode for FunctionExpr | Function outer | builtin-class function | +| 88 | ControlFlowNode for outer | Function outer | builtin-class function | +| 89 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | +| 89 | ControlFlowNode for y | int 1 | builtin-class int | +| 90 | ControlFlowNode for FunctionExpr | Function inner | builtin-class function | +| 90 | ControlFlowNode for inner | Function inner | builtin-class function | +| 92 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | +| 92 | ControlFlowNode for z | int 2 | builtin-class int | +| 93 | ControlFlowNode for inner | Function inner | builtin-class function | +| 95 | ControlFlowNode for FunctionExpr | Function never_none | builtin-class function | +| 95 | ControlFlowNode for never_none | Function never_none | builtin-class function | +| 97 | ControlFlowNode for FloatLiteral | float 1.0 | builtin-class float | +| 97 | ControlFlowNode for y | float 1.0 | builtin-class float | +| 99 | ControlFlowNode for None | NoneType None | builtin-class NoneType | +| 99 | ControlFlowNode for y | NoneType None | builtin-class NoneType | +| 100 | ControlFlowNode for Compare | bool False | builtin-class bool | +| 100 | ControlFlowNode for Compare | bool True | builtin-class bool | +| 100 | ControlFlowNode for None | NoneType None | builtin-class NoneType | +| 100 | ControlFlowNode for y | NoneType None | builtin-class NoneType | +| 100 | ControlFlowNode for y | float 1.0 | builtin-class float | +| 101 | ControlFlowNode for FloatLiteral | float 0.0 | builtin-class float | +| 101 | ControlFlowNode for y | float 0.0 | builtin-class float | +| 102 | ControlFlowNode for y | float 0.0 | builtin-class float | +| 102 | ControlFlowNode for y | float 1.0 | builtin-class float | +| 104 | ControlFlowNode for FunctionExpr | Function outer_use_vars | builtin-class function | +| 104 | ControlFlowNode for outer_use_vars | Function outer_use_vars | builtin-class function | +| 105 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | +| 105 | ControlFlowNode for y | int 1 | builtin-class int | +| 106 | ControlFlowNode for FunctionExpr | Function inner | builtin-class function | +| 106 | ControlFlowNode for inner | Function inner | builtin-class function | +| 108 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | +| 108 | ControlFlowNode for z | int 2 | builtin-class int | +| 109 | ControlFlowNode for y | int 1 | builtin-class int | +| 109 | ControlFlowNode for z | int 2 | builtin-class int | +| 110 | ControlFlowNode for inner | Function inner | builtin-class function | +| 112 | ControlFlowNode for FunctionExpr | Function literals_in_func | builtin-class function | +| 112 | ControlFlowNode for literals_in_func | Function literals_in_func | builtin-class function | +| 113 | ControlFlowNode for True | bool True | builtin-class bool | +| 114 | ControlFlowNode for None | NoneType None | builtin-class NoneType | +| 115 | ControlFlowNode for IntegerLiteral | int 1346 | builtin-class int | +| 116 | ControlFlowNode for FloatLiteral | float 0.7 | builtin-class float | +| 117 | ControlFlowNode for ClassExpr | class X | builtin-class type | +| 117 | ControlFlowNode for X | class X | builtin-class type | +| 117 | ControlFlowNode for object | builtin-class object | builtin-class type | +| 118 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | +| 118 | ControlFlowNode for f | Function f | builtin-class function | +| 119 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | +| 120 | ControlFlowNode for List | List | builtin-class list | +| 122 | ControlFlowNode for Lambda | Function lambda | builtin-class function | +| 122 | ControlFlowNode for following | Function following | builtin-class function | +| 122 | ControlFlowNode for following() | NoneType None | builtin-class NoneType | +| 122 | ControlFlowNode for y | Function lambda | builtin-class function | +| 124 | ControlFlowNode for FunctionExpr | Function following | builtin-class function | +| 124 | ControlFlowNode for following | Function following | builtin-class function | +| 127 | ControlFlowNode for Dict | Dict | builtin-class dict | +| 127 | ControlFlowNode for FunctionExpr | Function params_and_defaults | builtin-class function | +| 127 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | +| 127 | ControlFlowNode for params_and_defaults | Function params_and_defaults | builtin-class function | +| 129 | ControlFlowNode for b | Dict | builtin-class dict | +| 130 | ControlFlowNode for c | int 1 | builtin-class int | +| 132 | ControlFlowNode for FunctionExpr | Function inner_cls | builtin-class function | +| 132 | ControlFlowNode for inner_cls | Function inner_cls | builtin-class function | +| 133 | ControlFlowNode for A | class A | builtin-class type | +| 133 | ControlFlowNode for BaseException | builtin-class BaseException | builtin-class type | +| 133 | ControlFlowNode for ClassExpr | class A | builtin-class type | +| 135 | ControlFlowNode for A | class A | builtin-class type | +| 135 | ControlFlowNode for A() | A() | class A | +| 135 | ControlFlowNode for a | A() | class A | +| 136 | ControlFlowNode for a | A() | class A | +| 138 | ControlFlowNode for ImportExpr | Module xyz | builtin-class module | +| 139 | ControlFlowNode for ImportExpr | Module xyz | builtin-class module | +| 139 | ControlFlowNode for xyz | Module xyz | builtin-class module | +| 140 | ControlFlowNode for Attribute | float 1.0 | builtin-class float | +| 140 | ControlFlowNode for xyz | Module xyz | builtin-class module | +| 141 | ControlFlowNode for z | float 3.0 | builtin-class float | +| 145 | ControlFlowNode for Base | class Base | builtin-class type | +| 145 | ControlFlowNode for ClassExpr | class Base | builtin-class type | +| 145 | ControlFlowNode for object | builtin-class object | builtin-class type | +| 147 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | +| 147 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | +| 148 | ControlFlowNode for Compare | bool False | builtin-class bool | +| 148 | ControlFlowNode for Compare | bool True | builtin-class bool | +| 148 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | +| 149 | ControlFlowNode for Attribute | class Derived1 | builtin-class type | +| 149 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | +| 149 | ControlFlowNode for self | self | class Base | +| 150 | ControlFlowNode for Compare | bool False | builtin-class bool | +| 150 | ControlFlowNode for Compare | bool True | builtin-class bool | +| 150 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | +| 151 | ControlFlowNode for Attribute | class Derived2 | builtin-class type | +| 151 | ControlFlowNode for Derived2 | class Derived2 | builtin-class type | +| 151 | ControlFlowNode for self | self | class Base | +| 153 | ControlFlowNode for Attribute | class Derived3 | builtin-class type | +| 153 | ControlFlowNode for Derived3 | class Derived3 | builtin-class type | +| 153 | ControlFlowNode for self | self | class Base | +| 155 | ControlFlowNode for Base | class Base | builtin-class type | +| 155 | ControlFlowNode for ClassExpr | class Derived1 | builtin-class type | +| 155 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | +| 158 | ControlFlowNode for Base | class Base | builtin-class type | +| 158 | ControlFlowNode for ClassExpr | class Derived2 | builtin-class type | +| 158 | ControlFlowNode for Derived2 | class Derived2 | builtin-class type | +| 161 | ControlFlowNode for Base | class Base | builtin-class type | +| 161 | ControlFlowNode for ClassExpr | class Derived3 | builtin-class type | +| 161 | ControlFlowNode for Derived3 | class Derived3 | builtin-class type | +| 164 | ControlFlowNode for Base | class Base | builtin-class type | +| 167 | ControlFlowNode for FunctionExpr | Function multiple_assignment | builtin-class function | +| 167 | ControlFlowNode for multiple_assignment | Function multiple_assignment | builtin-class function | +| 168 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | +| 168 | ControlFlowNode for _list | builtin-class list | builtin-class type | +| 168 | ControlFlowNode for _tuple | builtin-class tuple | builtin-class type | +| 168 | ControlFlowNode for list | builtin-class list | builtin-class type | +| 168 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type | +| 169 | ControlFlowNode for _tuple | builtin-class tuple | builtin-class type | +| 170 | ControlFlowNode for _list | builtin-class list | builtin-class type | +| 173 | ControlFlowNode for Base2 | class Base2 | builtin-class type | +| 173 | ControlFlowNode for ClassExpr | class Base2 | builtin-class type | +| 173 | ControlFlowNode for object | builtin-class object | builtin-class type | +| 175 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | +| 175 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | +| 178 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | +| 178 | ControlFlowNode for x | int 1 | builtin-class int | +| 180 | ControlFlowNode for Base2 | class Base2 | builtin-class type | +| 180 | ControlFlowNode for ClassExpr | class Derived4 | builtin-class type | +| 180 | ControlFlowNode for Derived4 | class Derived4 | builtin-class type | +| 182 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | +| 182 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | +| 183 | ControlFlowNode for Attribute | super().x | builtin-class method | +| 183 | ControlFlowNode for Derived4 | class Derived4 | builtin-class type | +| 183 | ControlFlowNode for self | self | class Derived4 | +| 183 | ControlFlowNode for super | builtin-class super | builtin-class type | +| 183 | ControlFlowNode for super() | super() | builtin-class super | +| 184 | ControlFlowNode for Attribute | super().__init__ | builtin-class method | +| 184 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | +| 184 | ControlFlowNode for Derived4 | class Derived4 | builtin-class type | +| 184 | ControlFlowNode for self | self | class Derived4 | +| 184 | ControlFlowNode for super | builtin-class super | builtin-class type | +| 184 | ControlFlowNode for super() | super() | builtin-class super | +| 187 | ControlFlowNode for FunctionExpr | Function vararg_kwarg | builtin-class function | +| 187 | ControlFlowNode for d | d | builtin-class dict | +| 187 | ControlFlowNode for t | t | builtin-class tuple | +| 187 | ControlFlowNode for vararg_kwarg | Function vararg_kwarg | builtin-class function | +| 188 | ControlFlowNode for t | t | builtin-class tuple | +| 189 | ControlFlowNode for d | d | builtin-class dict | +| 193 | ControlFlowNode for ClassExpr | class E | builtin-class type | +| 193 | ControlFlowNode for E | class E | builtin-class type | +| 193 | ControlFlowNode for object | builtin-class object | builtin-class type | +| 195 | ControlFlowNode for FunctionExpr | Function _internal | builtin-class function | +| 195 | ControlFlowNode for _internal | Function _internal | builtin-class function | +| 197 | ControlFlowNode for FunctionExpr | Function wrapper | builtin-class function | +| 197 | ControlFlowNode for wrapper | Function wrapper | builtin-class function | +| 199 | ControlFlowNode for wrapper | Function wrapper | builtin-class function | +| 201 | ControlFlowNode for _internal | Function _internal | builtin-class function | +| 201 | ControlFlowNode for _internal() | Function wrapper | builtin-class function | +| 202 | ControlFlowNode for FunctionExpr | Function method | builtin-class function | +| 202 | ControlFlowNode for args | args | builtin-class tuple | +| 202 | ControlFlowNode for method | Function wrapper | builtin-class function | +| 206 | ControlFlowNode for FunctionExpr | Function calls_next | builtin-class function | +| 206 | ControlFlowNode for calls_next | Function calls_next | builtin-class function | +| 207 | ControlFlowNode for iter | Builtin-function iter | builtin-class builtin_function_or_method | +| 208 | ControlFlowNode for next | Builtin-function next | builtin-class builtin_function_or_method | +| 213 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | +| 213 | ControlFlowNode for ImportMember | Builtin-function exit | builtin-class builtin_function_or_method | +| 213 | ControlFlowNode for exit | Builtin-function exit | builtin-class builtin_function_or_method | +| 217 | ControlFlowNode for None | NoneType None | builtin-class NoneType | +| 217 | ControlFlowNode for g1 | NoneType None | builtin-class NoneType | +| 219 | ControlFlowNode for FunctionExpr | Function assign_global | builtin-class function | +| 219 | ControlFlowNode for assign_global | Function assign_global | builtin-class function | +| 221 | ControlFlowNode for IntegerLiteral | int 101 | builtin-class int | +| 221 | ControlFlowNode for g1 | int 101 | builtin-class int | +| 222 | ControlFlowNode for g1 | int 101 | builtin-class int | +| 226 | ControlFlowNode for None | NoneType None | builtin-class NoneType | +| 226 | ControlFlowNode for g2 | NoneType None | builtin-class NoneType | +| 228 | ControlFlowNode for FunctionExpr | Function init | builtin-class function | +| 228 | ControlFlowNode for init | Function init | builtin-class function | +| 230 | ControlFlowNode for IntegerLiteral | int 102 | builtin-class int | +| 230 | ControlFlowNode for g2 | int 102 | builtin-class int | +| 232 | ControlFlowNode for init | Function init | builtin-class function | +| 232 | ControlFlowNode for init() | NoneType None | builtin-class NoneType | +| 233 | ControlFlowNode for g2 | int 102 | builtin-class int | +| 236 | ControlFlowNode for None | NoneType None | builtin-class NoneType | +| 236 | ControlFlowNode for g3 | NoneType None | builtin-class NoneType | +| 238 | ControlFlowNode for ClassExpr | class Ugly | builtin-class type | +| 238 | ControlFlowNode for Ugly | class Ugly | builtin-class type | +| 238 | ControlFlowNode for object | builtin-class object | builtin-class type | +| 240 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | +| 240 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | +| 242 | ControlFlowNode for IntegerLiteral | int 103 | builtin-class int | +| 242 | ControlFlowNode for g3 | int 103 | builtin-class int | +| 244 | ControlFlowNode for FunctionExpr | Function meth | builtin-class function | +| 244 | ControlFlowNode for meth | Function meth | builtin-class function | +| 245 | ControlFlowNode for g3 | int 103 | builtin-class int | +| 248 | ControlFlowNode for ClassExpr | class F | builtin-class type | +| 248 | ControlFlowNode for F | class F | builtin-class type | +| 248 | ControlFlowNode for object | builtin-class object | builtin-class type | +| 250 | ControlFlowNode for g3 | NoneType None | builtin-class NoneType | +| 251 | ControlFlowNode for g3 | NoneType None | builtin-class NoneType | +| 254 | ControlFlowNode for ClassExpr | class G | builtin-class type | +| 254 | ControlFlowNode for G | class G | builtin-class type | +| 254 | ControlFlowNode for object | builtin-class object | builtin-class type | +| 256 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | +| 256 | ControlFlowNode for attr | int 0 | builtin-class int | +| 258 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | +| 258 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | +| 259 | ControlFlowNode for Attribute | int 1 | builtin-class int | +| 259 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | +| 259 | ControlFlowNode for self | self | class G | +| 261 | ControlFlowNode for FunctionExpr | Function meth | builtin-class function | +| 261 | ControlFlowNode for meth | Function meth | builtin-class function | +| 262 | ControlFlowNode for Attribute | int 2 | builtin-class int | +| 262 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | +| 262 | ControlFlowNode for self | self | class G | +| 263 | ControlFlowNode for Attribute | int 3 | builtin-class int | +| 263 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | +| 263 | ControlFlowNode for self | self | class G | +| 264 | ControlFlowNode for Attribute | int 3 | builtin-class int | +| 264 | ControlFlowNode for self | self | class G | +| 267 | ControlFlowNode for Derived4 | class Derived4 | builtin-class type | +| 267 | ControlFlowNode for Derived4() | Derived4() | class Derived4 | diff --git a/python/ql/test/library-tests/PointsTo/general/LocalPointsToType.ql b/python/ql/test/library-tests/PointsTo/general/LocalPointsToType.ql new file mode 100644 index 000000000000..693d0b2b84bc --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/general/LocalPointsToType.ql @@ -0,0 +1,10 @@ + +import python +import interesting +import Util + +from int line, ControlFlowNode f, Object o, ClassObject cls +where + of_interest(f, line) and + f.refersTo(o, cls, _) +select line, f.toString(), repr(o), repr(cls) diff --git a/python/ql/test/library-tests/PointsTo/general/Util.qll b/python/ql/test/library-tests/PointsTo/general/Util.qll new file mode 100644 index 000000000000..f75ed24f4f0b --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/general/Util.qll @@ -0,0 +1,10 @@ +import python + +string repr(Object o) { + not o instanceof StringObject and not o = theBoundMethodType() and result = o.toString() + or + /* Work around differing names in 2/3 */ + result = "'" + o.(StringObject).getText() + "'" + or + o = theBoundMethodType() and result = "builtin-class method" +} diff --git a/python/ql/test/library-tests/PointsTo/general/interesting.qll b/python/ql/test/library-tests/PointsTo/general/interesting.qll new file mode 100644 index 000000000000..2a5259dd176d --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/general/interesting.qll @@ -0,0 +1,14 @@ + +import python + +predicate of_interest(ControlFlowNode n, int line) { + exists(Location l, File f | l = n.getLocation() | + line = l.getStartLine() and + f = l.getFile() and + f.getName().matches("%test.py%") and + exists(Comment c | + c.getLocation().getStartLine() < line and + c.getLocation().getFile() = f + ) + ) +} diff --git a/python/ql/test/library-tests/PointsTo/general/options b/python/ql/test/library-tests/PointsTo/general/options new file mode 100644 index 000000000000..b91afde07678 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/general/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=2 diff --git a/python/ql/test/library-tests/PointsTo/general/pointsto_test.py b/python/ql/test/library-tests/PointsTo/general/pointsto_test.py new file mode 100644 index 000000000000..26fcce71ef9a --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/general/pointsto_test.py @@ -0,0 +1,267 @@ +from __future__ import unicode_literals +import sys +class C(object): + + x = 'C_x' + + def __init__(self): + self.y = 'c_y' + +class D(object): + + x = 'D_x' + + def __init__(self): + self.y = 'd_y' +#Comment here +if len(sys.argv) > 2: + v1 = C +else: + v1 = D +v2 = v1() + +def f(): + if len(sys.argv) > 3: + v3 = C() + else: + v3 = D() + return v3 + +def g(arg): + return arg + +v4 = g(f()) + +class X(object): + @classmethod + def method1(cls): + pass + + @deco + def method2(self): + pass + +def deco(f): + return f + +v1 +v2 +v3 +v4 +list + +def h(args): + if len(sys.argv) > 4: + v5 = C() + else: + v5 = D() + return v5, list(args) + +def j(): + return tuple, dict +dict +dict = 7 +dict +tuple = tuple +tuple + + +X.method1 +X.method2 + +from abc import abstractmethod +abstractmethod + +type(C()) +type(sys) +from module import unknown +type(unknown) +type(name, (object,), {}) + +def k(arg): + type(C()) + type(sys) + type(arg) + type(name, (object,), {}) + +#Value of variables in inner functions +def outer(x): + y = 1 + def inner(): + return y + z + z = 2; + return inner + +def never_none(x): + if test(x): + y = 1.0 + else: + y = None + if y is None: + y = 0.0 + return y + +def outer_use_vars(x): + y = 1 + def inner(): + return y + z + z = 2; + y + z + return inner + +def literals_in_func(): + True + None + 1346 + 0.7 + class X(object): pass + def f(): pass + (a, b) + [a, b] + +y = lambda x : following() + +def following(): + pass + +def params_and_defaults(a, b={}, c = 1): + a + b + c + +def inner_cls(): + class A(BaseException): + pass + a = A() + raise a + +from xyz import * +import xyz +xyz.x +z + +#ODASA-3263 +#Django does this +class Base(object): + + def __init__(self, choice): + if choice == 1: + self.__class__ = Derived1 + elif choice == 2: + self.__class__ = Derived2 + else: + self.__class__ = Derived3 + +class Derived1(Base): + pass + +class Derived2(Base): + pass + +class Derived3(Base): + pass + +thing = Base(unknown()) + + +def multiple_assignment(): + _tuple, _list = tuple, list + _tuple + _list + + +class Base2(object): + + def __init__(self): + pass + + x = 1 + +class Derived4(Base2): + + def __init__(self): + super(Derived4, self).x + return super(Derived4, self).__init__() + + +def vararg_kwarg(*t, **d): + t + d + + +#ODASA-4055 +class E(object): + + def _internal(arg): + # arg is not a C + def wrapper(args): + return arg(args) + return wrapper + + @_internal + def method(self, *args): + pass + +#Builtin function calls +def calls_next(seq): + it = iter(seq) + n = next(it) + return n + + +#Check imports from builtin modules +from sys import exit + + +#Global assignment in local scope +g1 = None + +def assign_global(): + global g1 + g1 = 101 + return g1 # Cannot be None + +#Assignment in local scope, but called from module level + +g2 = None + +def init(): + global g2 + g2 = 102 # Cannot be None + +init() +g2 # Cannot be None + +#Global set in init method +g3 = None + +class Ugly(object): + + def __init__(self): + global g3 + g3 = 103 + + def meth(self): + return g3 # Cannot be None + +#Global in class scope +class F(object): + + g3 = g3 + g3 + +#Locally redefined attribute +class G(object): + + attr = 0 + + def __init__(self): + self.attr = 1 + + def meth(self): + self.attr = 2 + self.attr = 3 + self.attr + +# Self can only be of a class that is instantiated. +Derived4() diff --git a/python/ql/test/library-tests/PointsTo/general/xyz.py b/python/ql/test/library-tests/PointsTo/general/xyz.py new file mode 100644 index 000000000000..392054917df4 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/general/xyz.py @@ -0,0 +1,4 @@ + +x = 1.0 +y = 2.0 +z = 3.0 diff --git a/python/ql/test/library-tests/PointsTo/guarded/PointsTo.expected b/python/ql/test/library-tests/PointsTo/guarded/PointsTo.expected new file mode 100644 index 000000000000..780529a795d0 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/guarded/PointsTo.expected @@ -0,0 +1,77 @@ +| test.py | 8 | ControlFlowNode for x | int 7 | 7 | +| test.py | 14 | ControlFlowNode for x | NoneType None | 10 | +| test.py | 14 | ControlFlowNode for x | int 7 | 13 | +| test.py | 20 | ControlFlowNode for x | NoneType None | 19 | +| test.py | 26 | ControlFlowNode for x | int 7 | 25 | +| test.py | 32 | ControlFlowNode for x | NoneType None | 28 | +| test.py | 32 | ControlFlowNode for x | int 7 | 31 | +| test.py | 38 | ControlFlowNode for x | NoneType None | 37 | +| test.py | 43 | ControlFlowNode for i | float 1.0 | 42 | +| test.py | 48 | ControlFlowNode for i | int 0 | 45 | +| test.py | 53 | ControlFlowNode for i | int 0 | 52 | +| test.py | 58 | ControlFlowNode for i | float 1.0 | 57 | +| test.py | 63 | ControlFlowNode for i | float 1.0 | 62 | +| test.py | 63 | ControlFlowNode for i | int 0 | 60 | +| test.py | 68 | ControlFlowNode for i | int 0 | 67 | +| test.py | 74 | ControlFlowNode for i | float 1.0 | 73 | +| test.py | 79 | ControlFlowNode for i | int 7 | 76 | +| test.py | 84 | ControlFlowNode for i | int 7 | 83 | +| test.py | 90 | ControlFlowNode for b | int 7 | 89 | +| test.py | 96 | ControlFlowNode for b | bool True | 92 | +| test.py | 96 | ControlFlowNode for b | int 7 | 95 | +| test.py | 103 | ControlFlowNode for b | bool False | 99 | +| test.py | 103 | ControlFlowNode for b | int 7 | 102 | +| test.py | 109 | ControlFlowNode for b | int 7 | 108 | +| test.py | 114 | ControlFlowNode for t | builtin-class type | 111 | +| test.py | 119 | ControlFlowNode for t | builtin-class object | 118 | +| test.py | 125 | ControlFlowNode for u | int 7 | 124 | +| test.py | 131 | ControlFlowNode for u | int 7 | 130 | +| test.py | 137 | ControlFlowNode for u | int 7 | 136 | +| test.py | 143 | ControlFlowNode for u | int 7 | 142 | +| test.py | 151 | ControlFlowNode for u | int 7 | 150 | +| test.py | 157 | ControlFlowNode for u | int 7 | 156 | +| test.py | 164 | ControlFlowNode for s | float 1.0 | 163 | +| test.py | 169 | ControlFlowNode for f | int 0 | 168 | +| test.py | 176 | ControlFlowNode for x | int 0 | 175 | +| test.py | 181 | ControlFlowNode for x | int 0 | 180 | +| test.py | 186 | ControlFlowNode for x | int 0 | 185 | +| test.py | 186 | ControlFlowNode for x | object() | 172 | +| test.py | 199 | ControlFlowNode for f | int 0 | 198 | +| test.py | 207 | ControlFlowNode for s | builtin-class type | 206 | +| test.py | 214 | ControlFlowNode for s | class C2 | 202 | +| test.py | 215 | ControlFlowNode for s | builtin-class type | 212 | +| test.py | 215 | ControlFlowNode for s | class C2 | 202 | +| test.py | 220 | ControlFlowNode for s | int 0 | 219 | +| test.py | 234 | ControlFlowNode for f | int 0 | 233 | +| test.py | 252 | ControlFlowNode for Attribute | int 1 | 242 | +| test.py | 254 | ControlFlowNode for Attribute | int 2 | 245 | +| test.py | 272 | ControlFlowNode for x | int 1 | 271 | +| test.py | 276 | ControlFlowNode for Attribute | int 1 | 271 | +| test.py | 286 | ControlFlowNode for y | NoneType None | 281 | +| test.py | 297 | ControlFlowNode for y | NoneType None | 291 | +| test.py | 301 | ControlFlowNode for x | NoneType None | 291 | +| test.py | 308 | ControlFlowNode for z | int 7 | 305 | +| test.py | 314 | ControlFlowNode for b | NoneType None | 311 | +| test.py | 332 | ControlFlowNode for Attribute | int 4 | 322 | +| test.py | 339 | ControlFlowNode for Attribute | int 4 | 322 | +| test.py | 347 | ControlFlowNode for Attribute | int 4 | 322 | +| test.py | 369 | ControlFlowNode for g2 | float 2.0 | 366 | +| test.py | 382 | ControlFlowNode for g3 | bool True | 379 | +| test.py | 389 | ControlFlowNode for g4 | int 7 | 396 | +| test.py | 408 | ControlFlowNode for x | int 1 | 404 | +| test.py | 420 | ControlFlowNode for Attribute | NoneType None | 418 | +| test.py | 427 | ControlFlowNode for Attribute | NoneType None | 418 | +| type_test.py | 5 | ControlFlowNode for d | Dict | 2 | +| type_test.py | 14 | ControlFlowNode for x | int 0 | 11 | +| type_test.py | 16 | ControlFlowNode for x | float 1.0 | 11 | +| type_test.py | 22 | ControlFlowNode for arg | builtin-class int | 20 | +| type_test.py | 35 | ControlFlowNode for arg | E() | 32 | +| type_test.py | 42 | ControlFlowNode for arg | E() | 39 | +| type_test.py | 49 | ControlFlowNode for arg | class E | 29 | +| type_test.py | 55 | ControlFlowNode for arg | class E | 29 | +| type_test.py | 67 | ControlFlowNode for x | float 1.0 | 62 | +| type_test.py | 67 | ControlFlowNode for x | int 0 | 62 | +| type_test.py | 77 | ControlFlowNode for IntegerLiteral | int 0 | 77 | +| type_test.py | 83 | ControlFlowNode for IntegerLiteral | int 0 | 83 | +| type_test.py | 89 | ControlFlowNode for IntegerLiteral | int 0 | 89 | +| type_test.py | 95 | ControlFlowNode for IntegerLiteral | int 0 | 95 | diff --git a/python/ql/test/library-tests/PointsTo/guarded/PointsTo.ql b/python/ql/test/library-tests/PointsTo/guarded/PointsTo.ql new file mode 100644 index 000000000000..98644b02e991 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/guarded/PointsTo.ql @@ -0,0 +1,7 @@ +import python + +from ControlFlowNode f, Object o, ControlFlowNode x + +where f.refersTo(o, x) and exists(CallNode call | call.getFunction().getNode().(Name).getId() = "use" and call.getArg(0) = f) + +select f.getLocation().getFile().getShortName(), f.getLocation().getStartLine(), f.toString(), o.toString(), x.getLocation().getStartLine() diff --git a/python/ql/test/library-tests/PointsTo/guarded/PointsToWithType.expected b/python/ql/test/library-tests/PointsTo/guarded/PointsToWithType.expected new file mode 100644 index 000000000000..bd9e52a76644 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/guarded/PointsToWithType.expected @@ -0,0 +1,77 @@ +| test.py | 8 | ControlFlowNode for x | int 7 | builtin-class int | 7 | +| test.py | 14 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 10 | +| test.py | 14 | ControlFlowNode for x | int 7 | builtin-class int | 13 | +| test.py | 20 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 19 | +| test.py | 26 | ControlFlowNode for x | int 7 | builtin-class int | 25 | +| test.py | 32 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 28 | +| test.py | 32 | ControlFlowNode for x | int 7 | builtin-class int | 31 | +| test.py | 38 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 37 | +| test.py | 43 | ControlFlowNode for i | float 1.0 | builtin-class float | 42 | +| test.py | 48 | ControlFlowNode for i | int 0 | builtin-class int | 45 | +| test.py | 53 | ControlFlowNode for i | int 0 | builtin-class int | 52 | +| test.py | 58 | ControlFlowNode for i | float 1.0 | builtin-class float | 57 | +| test.py | 63 | ControlFlowNode for i | float 1.0 | builtin-class float | 62 | +| test.py | 63 | ControlFlowNode for i | int 0 | builtin-class int | 60 | +| test.py | 68 | ControlFlowNode for i | int 0 | builtin-class int | 67 | +| test.py | 74 | ControlFlowNode for i | float 1.0 | builtin-class float | 73 | +| test.py | 79 | ControlFlowNode for i | int 7 | builtin-class int | 76 | +| test.py | 84 | ControlFlowNode for i | int 7 | builtin-class int | 83 | +| test.py | 90 | ControlFlowNode for b | int 7 | builtin-class int | 89 | +| test.py | 96 | ControlFlowNode for b | bool True | builtin-class bool | 92 | +| test.py | 96 | ControlFlowNode for b | int 7 | builtin-class int | 95 | +| test.py | 103 | ControlFlowNode for b | bool False | builtin-class bool | 99 | +| test.py | 103 | ControlFlowNode for b | int 7 | builtin-class int | 102 | +| test.py | 109 | ControlFlowNode for b | int 7 | builtin-class int | 108 | +| test.py | 114 | ControlFlowNode for t | builtin-class type | builtin-class type | 111 | +| test.py | 119 | ControlFlowNode for t | builtin-class object | builtin-class type | 118 | +| test.py | 125 | ControlFlowNode for u | int 7 | builtin-class int | 124 | +| test.py | 131 | ControlFlowNode for u | int 7 | builtin-class int | 130 | +| test.py | 137 | ControlFlowNode for u | int 7 | builtin-class int | 136 | +| test.py | 143 | ControlFlowNode for u | int 7 | builtin-class int | 142 | +| test.py | 151 | ControlFlowNode for u | int 7 | builtin-class int | 150 | +| test.py | 157 | ControlFlowNode for u | int 7 | builtin-class int | 156 | +| test.py | 164 | ControlFlowNode for s | float 1.0 | builtin-class float | 163 | +| test.py | 169 | ControlFlowNode for f | int 0 | builtin-class int | 168 | +| test.py | 176 | ControlFlowNode for x | int 0 | builtin-class int | 175 | +| test.py | 181 | ControlFlowNode for x | int 0 | builtin-class int | 180 | +| test.py | 186 | ControlFlowNode for x | int 0 | builtin-class int | 185 | +| test.py | 186 | ControlFlowNode for x | object() | builtin-class object | 172 | +| test.py | 199 | ControlFlowNode for f | int 0 | builtin-class int | 198 | +| test.py | 207 | ControlFlowNode for s | builtin-class type | builtin-class type | 206 | +| test.py | 214 | ControlFlowNode for s | class C2 | builtin-class type | 202 | +| test.py | 215 | ControlFlowNode for s | builtin-class type | builtin-class type | 212 | +| test.py | 215 | ControlFlowNode for s | class C2 | builtin-class type | 202 | +| test.py | 220 | ControlFlowNode for s | int 0 | builtin-class int | 219 | +| test.py | 234 | ControlFlowNode for f | int 0 | builtin-class int | 233 | +| test.py | 252 | ControlFlowNode for Attribute | int 1 | builtin-class int | 242 | +| test.py | 254 | ControlFlowNode for Attribute | int 2 | builtin-class int | 245 | +| test.py | 272 | ControlFlowNode for x | int 1 | builtin-class int | 271 | +| test.py | 276 | ControlFlowNode for Attribute | int 1 | builtin-class int | 271 | +| test.py | 286 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 281 | +| test.py | 297 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 291 | +| test.py | 301 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 291 | +| test.py | 308 | ControlFlowNode for z | int 7 | builtin-class int | 305 | +| test.py | 314 | ControlFlowNode for b | NoneType None | builtin-class NoneType | 311 | +| test.py | 332 | ControlFlowNode for Attribute | int 4 | builtin-class int | 322 | +| test.py | 339 | ControlFlowNode for Attribute | int 4 | builtin-class int | 322 | +| test.py | 347 | ControlFlowNode for Attribute | int 4 | builtin-class int | 322 | +| test.py | 369 | ControlFlowNode for g2 | float 2.0 | builtin-class float | 366 | +| test.py | 382 | ControlFlowNode for g3 | bool True | builtin-class bool | 379 | +| test.py | 389 | ControlFlowNode for g4 | int 7 | builtin-class int | 396 | +| test.py | 408 | ControlFlowNode for x | int 1 | builtin-class int | 404 | +| test.py | 420 | ControlFlowNode for Attribute | NoneType None | builtin-class NoneType | 418 | +| test.py | 427 | ControlFlowNode for Attribute | NoneType None | builtin-class NoneType | 418 | +| type_test.py | 5 | ControlFlowNode for d | Dict | builtin-class dict | 2 | +| type_test.py | 14 | ControlFlowNode for x | int 0 | builtin-class int | 11 | +| type_test.py | 16 | ControlFlowNode for x | float 1.0 | builtin-class float | 11 | +| type_test.py | 22 | ControlFlowNode for arg | builtin-class int | builtin-class type | 20 | +| type_test.py | 35 | ControlFlowNode for arg | E() | class E | 32 | +| type_test.py | 42 | ControlFlowNode for arg | E() | class E | 39 | +| type_test.py | 49 | ControlFlowNode for arg | class E | builtin-class type | 29 | +| type_test.py | 55 | ControlFlowNode for arg | class E | builtin-class type | 29 | +| type_test.py | 67 | ControlFlowNode for x | float 1.0 | builtin-class float | 62 | +| type_test.py | 67 | ControlFlowNode for x | int 0 | builtin-class int | 62 | +| type_test.py | 77 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 77 | +| type_test.py | 83 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 83 | +| type_test.py | 89 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 89 | +| type_test.py | 95 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 95 | diff --git a/python/ql/test/library-tests/PointsTo/guarded/PointsToWithType.ql b/python/ql/test/library-tests/PointsTo/guarded/PointsToWithType.ql new file mode 100644 index 000000000000..83bbd5e42ba4 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/guarded/PointsToWithType.ql @@ -0,0 +1,7 @@ +import python + +from ControlFlowNode f, Object o, ClassObject c, ControlFlowNode x + +where f.refersTo(o, c, x) and exists(CallNode call | call.getFunction().getNode().(Name).getId() = "use" and call.getArg(0) = f) + +select f.getLocation().getFile().getShortName(), f.getLocation().getStartLine(), f.toString(), o.toString(), c.toString(), x.getLocation().getStartLine() diff --git a/python/ql/test/library-tests/PointsTo/guarded/options b/python/ql/test/library-tests/PointsTo/guarded/options new file mode 100644 index 000000000000..be048160aeb9 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/guarded/options @@ -0,0 +1,2 @@ +semmle-extractor-options: --max-import-depth=3 +optimize: true diff --git a/python/ql/test/library-tests/PointsTo/guarded/test.py b/python/ql/test/library-tests/PointsTo/guarded/test.py new file mode 100644 index 000000000000..ced4edc0b6c6 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/guarded/test.py @@ -0,0 +1,428 @@ +#Edge guards. + +def f(): + x = None + + if x is None: + x = 7 + use(x) + + x = unknown() if cond else None + + if x is not None: + x = 7 + use(x) + + x = None + + if x is None: + x = None + use(x) + + x = None + + if not x: + x = 7 + use(x) + + x = unknown() if cond else None + + if x: + x = 7 + use(x) + + x = None + + if not x: + x = None + use(x) + + i = 0 + if i == 0: + i = 1.0 + use(i) + + i = 0 + if i != 0: + i = 1.0 + use(i) + + i = 0 + if i == 0: + i = 0 + use(i) + + i = 0 + if not i: + i = 1.0 + use(i) + + i = unknown() if cond else 0 + if i: + i = 1.0 + use(i) + + i = 0 + if not i: + i = 0 + use(i) + + + i = 7 + if i == 7: + i = 1.0 + use(i) + + i = 7 + if i != 7: + i = 1.0 + use(i) + + i = 7 + if i == 7: + i = 7 + use(i) + + b = True + + if b: + b = 7 + use(b) + + b = unknown() if cond else True + + if not b: + b = 7 + use(b) + + + b = unknown() if cond else False + + if b: + b = 7 + use(b) + + b = False + + if not b: + b = 7 + use(b) + + t = type + if t is object: + t = float + use(t) + + t = type + if t is not object: + t = object + use(t) + + u = unknown_thing() + + if u is None: + u = 7 + use(u) + + u = unknown_thing() + + if u is not None: + u = 7 + use(u) + + u = unknown_thing() + + if u: + u = 7 + use(u) + + u = unknown_thing() + + if not u: + u = 7 + use(u) + + K = unknown_thing() + + u = unknown_thing() + + if u is K: + u = 7 + use(u) + + u = unknown_thing() + + if u is not K: + u = 7 + use(u) + +#String and float consts. + + s = "not this" + if s == "not this": + s = 1.0 + use(s) + + f = 0.7 + if f == 0.7: + f = 0 + use(f) + +#Sentinel guards +SENTINEL = object() +def g(x = SENTINEL): + if x is SENTINEL: + x = 0 + use(x) + +def h(x = SENTINEL): + if x == SENTINEL: + x = 0 + use(x) + +def j(x = SENTINEL): + if x is not SENTINEL: + x = 0 + use(x) + +#ODASA-4056 +def format_string(s, formatter='minimal'): + """Format the given string using the given formatter.""" + if not callable(formatter): + formatter = get_formatter_for_name(formatter) + use(formatter) + +def guard_callable(s, f=j): + """Format the given string using the given formatter.""" + if callable(f): + f=0 + use(f) + +class C1(object):pass +class C2(C1):pass + +def guard_subclass(s = C2): + if issubclass(s, C1): + s = type + use(s) + +def guard_subclass2(s = C2): + if not issubclass(s, C1): + use(s) + s = type + else: + use(s) + use(s) + +def instance_guard(s, f=1.0): + if isinstance(s, float): + s = 0 + use(s) + + +#ODASA-4056 +def format_string(s, formatter='minimal'): + """Format the given string using the given formatter.""" + if not callable(formatter): + formatter = get_formatter_for_name(formatter) + use(formatter) + +func_type = type(j) +def guard_callable(s, f=j): + if isinstance(f, func_type): + f=0 + use(f) + + +#Attribute points-to +class C(object): + + def __init__(self): + self._init() + self.x = 1 + + def _init(self): + self.y = 2 + self._init2() + + def _init2(self): + self.z = 3 + + def method(self): + use(self.x) + if isinstance(self.y, int): + use(self.y) + if not isinstance(self.z, int): + use(self.z) + +#Guarded None in nested function +def f(x=None): + def inner(arg): + if x: + use(x) + + + +#Guards on whole scope... +class D(object): + + def __init__(self, x = None): + if x is None: + x = 1 + use(x) + self.x = x + + def f(self): + use(self.x) + +#Biased splitting & pruning +def f(cond): + if cond: + y = None + x = False + else: + y = something() + x = some_condition() + use(y) # y can be None here + if x: + use(y) # Should not infer that y is None here + +#Splittings with boolean expressions: +def split_bool1(x=None,y=None): + if x and y: + raise + if not (x or y): + raise + if x: + use(y) + else: + use(y) + if y: + use(x) + else: + use(x) + +def split_bool2(x,y=None,z=7): + if x and not y or x and use(y): + pass + if x and not z or x and use(z): + pass + +def split_bool3(a=None, b=None): + if a or b: + if a: + use(b) + else: + use(b) # Should not infer b to be None. + +#Guard on instance attribute +class E(object): + + def __init__(self): + self.y = None if rand() else 4 + + x = None if rand() else 3 + + def a(self): + e = E() + #Do not mutate + if e.x: + use(e.x) + if e.y: + use(e.y) + + def b(self): + possibly_mutate(self) + if self.x: + use(self.x) + if self.y: + use(self.y) + +def k(): + e = E() + possibly_mutate(e) + if e.x: + use(e.x) + if e.y: + use(e.y) + + +#Global assignment in local scope +g1 = None + +def assign_global(): + global g1 + if not g1: + g1 = 7.0 + use(g1) # Cannot be None + +#Assignment in local scope, but called from module level + +g2 = None + +def init(): + global g2 + if not g2: + g2 = 2.0 + +init() +use(g2) # Cannot be None + +#Global set in init method +g3 = None + +class Ugly(object): + + def __init__(self): + global g3 + if not g3: + g3 = True + + def meth(self): + use(g3) # Cannot be None + +g4 = None + +def get_g4(): + if not g4: + set_g4() + use(g4) # Cannot be None + +def set_g4(): + set_g4_indirect() + +def set_g4_indirect(): + global g4 + g4 = 7 + +#Assertions +def f(cond): + x = None if cond else thing() + assert x + use(x) + +def f(cond, x = 1): + if cond: + x = 1.0 + assert isinstance(x, int) + use(x) + +#ODASA-5018 +def f(x,y=None, z=0): + if (x and y) or (y and not z): + #y cannot be None here. + use(y) + +class C(object): + + def __init__(self, x=None): + self.x = x + use(self.x) + + def meth(self): + if self.x is not None: + use(self.x) + return lambda : use(self.x) + else: + use(self.x) + return lambda : use(self.x) diff --git a/python/ql/test/library-tests/PointsTo/guarded/type_test.py b/python/ql/test/library-tests/PointsTo/guarded/type_test.py new file mode 100644 index 000000000000..79597dee0efa --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/guarded/type_test.py @@ -0,0 +1,97 @@ + +def f(d = {}): + + if isinstance(d, dict): + use(d) + else: + use(d) + +def g(cond): + + x = 0 if cond else 1.0 + + if isinstance(x, int): + use(x) + elif isinstance(x, float): + use(x) + else: + use(x) + +def h(arg=int): + if issubclass(arg, int): + use(arg) + else: + use(arg) + +class D(object): + pass + +class E(D): + pass + +def j(arg=E()): + + if isinstance(arg, E): + use(arg) + else: + use(arg) + +def k(arg=E()): + + if isinstance(arg, D): + use(arg) + else: + use(arg) + + +def l(arg=E): + if issubclass(arg, E): + use(arg) + else: + use(arg) + +def m(arg=E): + if issubclass(arg, D): + use(arg) + else: + use(arg) + +number = int, float + +def n(cond): + x = 0 if cond else 1.0 + + if not isinstance(x, number): + use(x) + else: + use(x) + +import sys +if sys.version < "3": + from collections import Iterable, Sequence, Set +else: + from collections.abc import Iterable, Sequence, Set + +def p(): + if issubclass(list, Iterable): + use(0) + else: + use(1) + +def q(): + if issubclass(list, Sequence): + use(0) + else: + use(1) + +def p(): + if isinstance({0}, Iterable): + use(0) + else: + use(1) + +def q(): + if isinstance({0}, Set): + use(0) + else: + use(1) diff --git a/python/ql/test/library-tests/PointsTo/imports/Runtime.expected b/python/ql/test/library-tests/PointsTo/imports/Runtime.expected new file mode 100644 index 000000000000..b36ac923ad4a --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/imports/Runtime.expected @@ -0,0 +1,55 @@ +| __init__.py | 1 | ControlFlowNode for ImportExpr | Module package.module | ControlFlowNode for ImportExpr | +| __init__.py | 2 | ControlFlowNode for ImportMember | Function module | ControlFlowNode for FunctionExpr | +| __init__.py | 2 | ControlFlowNode for module | Function module | ControlFlowNode for FunctionExpr | +| __init__.py | 4 | ControlFlowNode for ImportExpr | Module package | ControlFlowNode for ImportExpr | +| __init__.py | 4 | ControlFlowNode for ImportMember | Module package.module2 | Entry node for Module package.module2 | +| __init__.py | 4 | ControlFlowNode for module3 | Module package.module2 | Entry node for Module package.module2 | +| __init__.py | 5 | ControlFlowNode for IntegerLiteral | int 7 | ControlFlowNode for IntegerLiteral | +| __init__.py | 5 | ControlFlowNode for module2 | int 7 | ControlFlowNode for IntegerLiteral | +| __init__.py | 6 | ControlFlowNode for ImportExpr | Module package | ControlFlowNode for ImportExpr | +| __init__.py | 6 | ControlFlowNode for ImportMember | int 7 | ControlFlowNode for IntegerLiteral | +| __init__.py | 6 | ControlFlowNode for module4 | int 7 | ControlFlowNode for IntegerLiteral | +| __init__.py | 7 | ControlFlowNode for ImportExpr | Module package | ControlFlowNode for ImportExpr | +| __init__.py | 7 | ControlFlowNode for ImportMember | Module package.module2 | Entry node for Module package.module2 | +| __init__.py | 7 | ControlFlowNode for module5 | Module package.module2 | Entry node for Module package.module2 | +| __init__.py | 8 | ControlFlowNode for ImportExpr | Module package | ControlFlowNode for ImportExpr | +| __init__.py | 8 | ControlFlowNode for ImportMember | Module package.moduleX | Entry node for Module package.moduleX | +| __init__.py | 8 | ControlFlowNode for moduleX | Module package.moduleX | Entry node for Module package.moduleX | +| module2.py | 1 | ControlFlowNode for IntegerLiteral | int 0 | ControlFlowNode for IntegerLiteral | +| module2.py | 1 | ControlFlowNode for x | int 0 | ControlFlowNode for IntegerLiteral | +| module.py | 2 | ControlFlowNode for FunctionExpr | Function module | ControlFlowNode for FunctionExpr | +| module.py | 2 | ControlFlowNode for module | Function module | ControlFlowNode for FunctionExpr | +| moduleX.py | 1 | ControlFlowNode for ClassExpr | class Y | ControlFlowNode for ClassExpr | +| moduleX.py | 1 | ControlFlowNode for Y | class Y | ControlFlowNode for ClassExpr | +| moduleX.py | 1 | ControlFlowNode for object | builtin-class object | ControlFlowNode for object | +| test.py | 1 | ControlFlowNode for ImportExpr | Module package | ControlFlowNode for ImportExpr | +| test.py | 2 | ControlFlowNode for ImportMember | Function module | ControlFlowNode for FunctionExpr | +| test.py | 2 | ControlFlowNode for module | Function module | ControlFlowNode for FunctionExpr | +| test.py | 4 | ControlFlowNode for ImportExpr | Module package | ControlFlowNode for ImportExpr | +| test.py | 5 | ControlFlowNode for ImportMember | Module package.x | Entry node for Module package.x | +| test.py | 5 | ControlFlowNode for x | Module package.x | Entry node for Module package.x | +| test.py | 8 | ControlFlowNode for C | class C | ControlFlowNode for ClassExpr | +| test.py | 8 | ControlFlowNode for ClassExpr | class C | ControlFlowNode for ClassExpr | +| test.py | 8 | ControlFlowNode for object | builtin-class object | ControlFlowNode for object | +| test.py | 10 | ControlFlowNode for ImportExpr | Module package | ControlFlowNode for ImportExpr | +| test.py | 10 | ControlFlowNode for ImportMember | int 7 | ControlFlowNode for IntegerLiteral | +| test.py | 10 | ControlFlowNode for module2 | int 7 | ControlFlowNode for IntegerLiteral | +| test.py | 12 | ControlFlowNode for FunctionExpr | Function f | ControlFlowNode for FunctionExpr | +| test.py | 12 | ControlFlowNode for f | Function f | ControlFlowNode for FunctionExpr | +| test.py | 13 | ControlFlowNode for ImportExpr | Module package | ControlFlowNode for ImportExpr | +| test.py | 13 | ControlFlowNode for ImportMember | Module package.x | Entry node for Module package.x | +| test.py | 13 | ControlFlowNode for x | Module package.x | Entry node for Module package.x | +| test.py | 15 | ControlFlowNode for ImportExpr | Module package | ControlFlowNode for ImportExpr | +| test.py | 15 | ControlFlowNode for ImportMember | Module package.moduleX | Entry node for Module package.moduleX | +| test.py | 15 | ControlFlowNode for moduleX | Module package.moduleX | Entry node for Module package.moduleX | +| test.py | 16 | ControlFlowNode for Attribute | class Y | ControlFlowNode for ClassExpr | +| test.py | 16 | ControlFlowNode for moduleX | Module package.moduleX | Entry node for Module package.moduleX | +| test.py | 19 | ControlFlowNode for ImportExpr | Module tty | ControlFlowNode for ImportExpr | +| test.py | 19 | ControlFlowNode for tty | Module tty | ControlFlowNode for ImportExpr | +| test.py | 22 | ControlFlowNode for Attribute | Builtin-function exc_info | ControlFlowNode for from sys import * | +| test.py | 22 | ControlFlowNode for x | Module package.x | Entry node for Module package.x | +| test.py | 24 | ControlFlowNode for IntegerLiteral | int 0 | ControlFlowNode for IntegerLiteral | +| test.py | 24 | ControlFlowNode for argv | int 0 | ControlFlowNode for IntegerLiteral | +| test.py | 27 | ControlFlowNode for ImportExpr | Module sys | ControlFlowNode for ImportExpr | +| test.py | 31 | ControlFlowNode for argv | list object | ControlFlowNode for argv | +| x.py | 2 | ControlFlowNode for ImportExpr | Module sys | ControlFlowNode for ImportExpr | diff --git a/python/ql/test/library-tests/PointsTo/imports/Runtime.ql b/python/ql/test/library-tests/PointsTo/imports/Runtime.ql new file mode 100644 index 000000000000..4a25bff744a9 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/imports/Runtime.ql @@ -0,0 +1,8 @@ + +import python + +from int line, ControlFlowNode f, Object o, ControlFlowNode orig +where + not f.getLocation().getFile().inStdlib() and + f.refersTo(o, orig) and line = f.getLocation().getStartLine() and line != 0 +select f.getLocation().getFile().getShortName(), line, f.toString(), o.toString(), orig.toString() diff --git a/python/ql/test/library-tests/PointsTo/imports/RuntimeWithType.expected b/python/ql/test/library-tests/PointsTo/imports/RuntimeWithType.expected new file mode 100644 index 000000000000..ebd971cc0487 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/imports/RuntimeWithType.expected @@ -0,0 +1,55 @@ +| __init__.py | 1 | ControlFlowNode for ImportExpr | Module package.module | builtin-class module | ControlFlowNode for ImportExpr | +| __init__.py | 2 | ControlFlowNode for ImportMember | Function module | builtin-class function | ControlFlowNode for FunctionExpr | +| __init__.py | 2 | ControlFlowNode for module | Function module | builtin-class function | ControlFlowNode for FunctionExpr | +| __init__.py | 4 | ControlFlowNode for ImportExpr | Module package | builtin-class module | ControlFlowNode for ImportExpr | +| __init__.py | 4 | ControlFlowNode for ImportMember | Module package.module2 | builtin-class module | Entry node for Module package.module2 | +| __init__.py | 4 | ControlFlowNode for module3 | Module package.module2 | builtin-class module | Entry node for Module package.module2 | +| __init__.py | 5 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | ControlFlowNode for IntegerLiteral | +| __init__.py | 5 | ControlFlowNode for module2 | int 7 | builtin-class int | ControlFlowNode for IntegerLiteral | +| __init__.py | 6 | ControlFlowNode for ImportExpr | Module package | builtin-class module | ControlFlowNode for ImportExpr | +| __init__.py | 6 | ControlFlowNode for ImportMember | int 7 | builtin-class int | ControlFlowNode for IntegerLiteral | +| __init__.py | 6 | ControlFlowNode for module4 | int 7 | builtin-class int | ControlFlowNode for IntegerLiteral | +| __init__.py | 7 | ControlFlowNode for ImportExpr | Module package | builtin-class module | ControlFlowNode for ImportExpr | +| __init__.py | 7 | ControlFlowNode for ImportMember | Module package.module2 | builtin-class module | Entry node for Module package.module2 | +| __init__.py | 7 | ControlFlowNode for module5 | Module package.module2 | builtin-class module | Entry node for Module package.module2 | +| __init__.py | 8 | ControlFlowNode for ImportExpr | Module package | builtin-class module | ControlFlowNode for ImportExpr | +| __init__.py | 8 | ControlFlowNode for ImportMember | Module package.moduleX | builtin-class module | Entry node for Module package.moduleX | +| __init__.py | 8 | ControlFlowNode for moduleX | Module package.moduleX | builtin-class module | Entry node for Module package.moduleX | +| module2.py | 1 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | ControlFlowNode for IntegerLiteral | +| module2.py | 1 | ControlFlowNode for x | int 0 | builtin-class int | ControlFlowNode for IntegerLiteral | +| module.py | 2 | ControlFlowNode for FunctionExpr | Function module | builtin-class function | ControlFlowNode for FunctionExpr | +| module.py | 2 | ControlFlowNode for module | Function module | builtin-class function | ControlFlowNode for FunctionExpr | +| moduleX.py | 1 | ControlFlowNode for ClassExpr | class Y | builtin-class type | ControlFlowNode for ClassExpr | +| moduleX.py | 1 | ControlFlowNode for Y | class Y | builtin-class type | ControlFlowNode for ClassExpr | +| moduleX.py | 1 | ControlFlowNode for object | builtin-class object | builtin-class type | ControlFlowNode for object | +| test.py | 1 | ControlFlowNode for ImportExpr | Module package | builtin-class module | ControlFlowNode for ImportExpr | +| test.py | 2 | ControlFlowNode for ImportMember | Function module | builtin-class function | ControlFlowNode for FunctionExpr | +| test.py | 2 | ControlFlowNode for module | Function module | builtin-class function | ControlFlowNode for FunctionExpr | +| test.py | 4 | ControlFlowNode for ImportExpr | Module package | builtin-class module | ControlFlowNode for ImportExpr | +| test.py | 5 | ControlFlowNode for ImportMember | Module package.x | builtin-class module | Entry node for Module package.x | +| test.py | 5 | ControlFlowNode for x | Module package.x | builtin-class module | Entry node for Module package.x | +| test.py | 8 | ControlFlowNode for C | class C | builtin-class type | ControlFlowNode for ClassExpr | +| test.py | 8 | ControlFlowNode for ClassExpr | class C | builtin-class type | ControlFlowNode for ClassExpr | +| test.py | 8 | ControlFlowNode for object | builtin-class object | builtin-class type | ControlFlowNode for object | +| test.py | 10 | ControlFlowNode for ImportExpr | Module package | builtin-class module | ControlFlowNode for ImportExpr | +| test.py | 10 | ControlFlowNode for ImportMember | int 7 | builtin-class int | ControlFlowNode for IntegerLiteral | +| test.py | 10 | ControlFlowNode for module2 | int 7 | builtin-class int | ControlFlowNode for IntegerLiteral | +| test.py | 12 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | ControlFlowNode for FunctionExpr | +| test.py | 12 | ControlFlowNode for f | Function f | builtin-class function | ControlFlowNode for FunctionExpr | +| test.py | 13 | ControlFlowNode for ImportExpr | Module package | builtin-class module | ControlFlowNode for ImportExpr | +| test.py | 13 | ControlFlowNode for ImportMember | Module package.x | builtin-class module | Entry node for Module package.x | +| test.py | 13 | ControlFlowNode for x | Module package.x | builtin-class module | Entry node for Module package.x | +| test.py | 15 | ControlFlowNode for ImportExpr | Module package | builtin-class module | ControlFlowNode for ImportExpr | +| test.py | 15 | ControlFlowNode for ImportMember | Module package.moduleX | builtin-class module | Entry node for Module package.moduleX | +| test.py | 15 | ControlFlowNode for moduleX | Module package.moduleX | builtin-class module | Entry node for Module package.moduleX | +| test.py | 16 | ControlFlowNode for Attribute | class Y | builtin-class type | ControlFlowNode for ClassExpr | +| test.py | 16 | ControlFlowNode for moduleX | Module package.moduleX | builtin-class module | Entry node for Module package.moduleX | +| test.py | 19 | ControlFlowNode for ImportExpr | Module tty | builtin-class module | ControlFlowNode for ImportExpr | +| test.py | 19 | ControlFlowNode for tty | Module tty | builtin-class module | ControlFlowNode for ImportExpr | +| test.py | 22 | ControlFlowNode for Attribute | Builtin-function exc_info | builtin-class builtin_function_or_method | ControlFlowNode for from sys import * | +| test.py | 22 | ControlFlowNode for x | Module package.x | builtin-class module | Entry node for Module package.x | +| test.py | 24 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | ControlFlowNode for IntegerLiteral | +| test.py | 24 | ControlFlowNode for argv | int 0 | builtin-class int | ControlFlowNode for IntegerLiteral | +| test.py | 27 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | ControlFlowNode for ImportExpr | +| test.py | 31 | ControlFlowNode for argv | list object | builtin-class list | ControlFlowNode for argv | +| x.py | 2 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | ControlFlowNode for ImportExpr | diff --git a/python/ql/test/library-tests/PointsTo/imports/RuntimeWithType.ql b/python/ql/test/library-tests/PointsTo/imports/RuntimeWithType.ql new file mode 100644 index 000000000000..eca5e965ea80 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/imports/RuntimeWithType.ql @@ -0,0 +1,8 @@ + +import python + +from int line, ControlFlowNode f, Object o, ClassObject cls, ControlFlowNode orig +where + not f.getLocation().getFile().inStdlib() and + f.refersTo(o, cls, orig) and line = f.getLocation().getStartLine() and line != 0 +select f.getLocation().getFile().getShortName(), line, f.toString(), o.toString(), cls.toString(), orig.toString() diff --git a/python/ql/test/library-tests/PointsTo/imports/options b/python/ql/test/library-tests/PointsTo/imports/options new file mode 100644 index 000000000000..b6a59fe27468 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/imports/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=2 -r package diff --git a/python/ql/test/library-tests/PointsTo/imports/package/__init__.py b/python/ql/test/library-tests/PointsTo/imports/package/__init__.py new file mode 100644 index 000000000000..715f4fbef35f --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/imports/package/__init__.py @@ -0,0 +1,15 @@ +from .module \ +import module + +from . import module2 as module3 +module2 = 7 +from . import module2 as module4 +from . import module3 as module5 +from package import moduleX + +#We should now have: +#module2 = 7 +#module3 = package.module2 +#module4 = 7 +#module5 = package.module2 +#moduleX = package.moduleX diff --git a/python/ql/test/library-tests/PointsTo/imports/package/module.py b/python/ql/test/library-tests/PointsTo/imports/package/module.py new file mode 100644 index 000000000000..008b713d67ef --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/imports/package/module.py @@ -0,0 +1,3 @@ + +def module(args): + pass diff --git a/python/ql/test/library-tests/PointsTo/imports/package/module2.py b/python/ql/test/library-tests/PointsTo/imports/package/module2.py new file mode 100644 index 000000000000..3aea0c58ce5d --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/imports/package/module2.py @@ -0,0 +1 @@ +x = 0 diff --git a/python/ql/test/library-tests/PointsTo/imports/package/moduleX.py b/python/ql/test/library-tests/PointsTo/imports/package/moduleX.py new file mode 100644 index 000000000000..3b39b8c0985c --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/imports/package/moduleX.py @@ -0,0 +1,2 @@ +class Y(object): + pass \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/imports/package/x.py b/python/ql/test/library-tests/PointsTo/imports/package/x.py new file mode 100644 index 000000000000..51cf8f5a381f --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/imports/package/x.py @@ -0,0 +1,2 @@ + +from sys import * diff --git a/python/ql/test/library-tests/PointsTo/imports/test.py b/python/ql/test/library-tests/PointsTo/imports/test.py new file mode 100644 index 000000000000..eb57051a8fa5 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/imports/test.py @@ -0,0 +1,31 @@ +from package \ +import module + +from package \ +import x +#Should work correctly in nested scopes as well. + +class C(object): + + from package import module2 + + def f(self): + from package import x + +from package import moduleX +moduleX.Y + +#A small stdlib module to test version handling. +import tty + +#Check imports of builtin-objects using import * with no corresponding variable. +x.exc_info + +argv = 0 + +try: + from sys import * +except: + pass + +argv diff --git a/python/ql/test/library-tests/PointsTo/indexing/Test.expected b/python/ql/test/library-tests/PointsTo/indexing/Test.expected new file mode 100644 index 000000000000..bd53dcea841b --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/indexing/Test.expected @@ -0,0 +1,27 @@ +| 1 | ControlFlowNode for ImportExpr | Module fake_collections | 1 | +| 1 | ControlFlowNode for ImportMember | class deque | 4 | +| 1 | ControlFlowNode for deque | class deque | 4 | +| 2 | ControlFlowNode for FunctionExpr | Function f | 2 | +| 2 | ControlFlowNode for f | Function f | 2 | +| 3 | ControlFlowNode for d | deque() | 3 | +| 3 | ControlFlowNode for deque | class deque | 4 | +| 3 | ControlFlowNode for deque() | deque() | 3 | +| 4 | ControlFlowNode for List | List | 4 | +| 4 | ControlFlowNode for l | List | 4 | +| 6 | ControlFlowNode for d | deque() | 3 | +| 7 | ControlFlowNode for l | List | 4 | +| 8 | ControlFlowNode for IntegerLiteral | int 1 | 8 | +| 8 | ControlFlowNode for t | int 1 | 8 | +| 9 | ControlFlowNode for IntegerLiteral | int 0 | 9 | +| 9 | ControlFlowNode for Subscript | int 1 | 8 | +| 9 | ControlFlowNode for d | deque() | 3 | +| 9 | ControlFlowNode for t | int 1 | 8 | +| 10 | ControlFlowNode for IntegerLiteral | int 0 | 10 | +| 10 | ControlFlowNode for Subscript | int 1 | 8 | +| 10 | ControlFlowNode for l | List | 4 | +| 10 | ControlFlowNode for t | int 1 | 8 | +| 11 | ControlFlowNode for d | deque() | 3 | +| 12 | ControlFlowNode for l | List | 4 | +| 13 | ControlFlowNode for d | deque() | 3 | +| 14 | ControlFlowNode for IntegerLiteral | int 0 | 14 | +| 14 | ControlFlowNode for d | deque() | 3 | diff --git a/python/ql/test/library-tests/PointsTo/indexing/Test.ql b/python/ql/test/library-tests/PointsTo/indexing/Test.ql new file mode 100644 index 000000000000..70b62e825f75 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/indexing/Test.ql @@ -0,0 +1,8 @@ +import python + +from ControlFlowNode f, Object o, ControlFlowNode x + +where f.refersTo(o, x) and +f.getLocation().getFile().getBaseName() = "test.py" + +select f.getLocation().getStartLine(), f.toString(), o.toString(), x.getLocation().getStartLine() diff --git a/python/ql/test/library-tests/PointsTo/indexing/TestWithType.expected b/python/ql/test/library-tests/PointsTo/indexing/TestWithType.expected new file mode 100644 index 000000000000..a542ba9fe862 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/indexing/TestWithType.expected @@ -0,0 +1,27 @@ +| 1 | ControlFlowNode for ImportExpr | Module fake_collections | builtin-class module | 1 | +| 1 | ControlFlowNode for ImportMember | class deque | builtin-class type | 4 | +| 1 | ControlFlowNode for deque | class deque | builtin-class type | 4 | +| 2 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 2 | +| 2 | ControlFlowNode for f | Function f | builtin-class function | 2 | +| 3 | ControlFlowNode for d | deque() | class deque | 3 | +| 3 | ControlFlowNode for deque | class deque | builtin-class type | 4 | +| 3 | ControlFlowNode for deque() | deque() | class deque | 3 | +| 4 | ControlFlowNode for List | List | builtin-class list | 4 | +| 4 | ControlFlowNode for l | List | builtin-class list | 4 | +| 6 | ControlFlowNode for d | deque() | class deque | 3 | +| 7 | ControlFlowNode for l | List | builtin-class list | 4 | +| 8 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 8 | +| 8 | ControlFlowNode for t | int 1 | builtin-class int | 8 | +| 9 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 9 | +| 9 | ControlFlowNode for Subscript | int 1 | builtin-class int | 8 | +| 9 | ControlFlowNode for d | deque() | class deque | 3 | +| 9 | ControlFlowNode for t | int 1 | builtin-class int | 8 | +| 10 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 10 | +| 10 | ControlFlowNode for Subscript | int 1 | builtin-class int | 8 | +| 10 | ControlFlowNode for l | List | builtin-class list | 4 | +| 10 | ControlFlowNode for t | int 1 | builtin-class int | 8 | +| 11 | ControlFlowNode for d | deque() | class deque | 3 | +| 12 | ControlFlowNode for l | List | builtin-class list | 4 | +| 13 | ControlFlowNode for d | deque() | class deque | 3 | +| 14 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 14 | +| 14 | ControlFlowNode for d | deque() | class deque | 3 | diff --git a/python/ql/test/library-tests/PointsTo/indexing/TestWithType.ql b/python/ql/test/library-tests/PointsTo/indexing/TestWithType.ql new file mode 100644 index 000000000000..6b0c8b8460d0 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/indexing/TestWithType.ql @@ -0,0 +1,8 @@ +import python + +from ControlFlowNode f, Object o, ClassObject c, ControlFlowNode x + +where f.refersTo(o, c, x) and +f.getLocation().getFile().getBaseName() = "test.py" + +select f.getLocation().getStartLine(), f.toString(), o.toString(), c.toString(), x.getLocation().getStartLine() diff --git a/python/ql/test/library-tests/PointsTo/indexing/fake_collections.py b/python/ql/test/library-tests/PointsTo/indexing/fake_collections.py new file mode 100644 index 000000000000..6f0ee9bde2dc --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/indexing/fake_collections.py @@ -0,0 +1,16 @@ +# Use a fake collection module, otherwise we need to set the import depth to 3 +# which makes the test run very slowly. + +class deque(object): + + def __getitem__(self, index): + pass + + def append(self, item): + pass + + def index(self, key): + pass + + def __reversed__(self): + pass diff --git a/python/ql/test/library-tests/PointsTo/indexing/options b/python/ql/test/library-tests/PointsTo/indexing/options new file mode 100644 index 000000000000..eb214fc2931a --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/indexing/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=1 diff --git a/python/ql/test/library-tests/PointsTo/indexing/test.py b/python/ql/test/library-tests/PointsTo/indexing/test.py new file mode 100644 index 000000000000..fba7bd91141c --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/indexing/test.py @@ -0,0 +1,14 @@ +from fake_collections import deque +def f(x, y, z): + d = deque() + l = [] + for i in seq: + d[x] = y + l[x] = z + t = 1 + d[0] = t + l[0] = t + d[i] + l[i] + d[x] + d[0] diff --git a/python/ql/test/library-tests/PointsTo/inheritance/BaseTypes.expected b/python/ql/test/library-tests/PointsTo/inheritance/BaseTypes.expected new file mode 100644 index 000000000000..24feada6fe18 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/BaseTypes.expected @@ -0,0 +1,15 @@ +| class Base | 0 | builtin-class object | +| class Derived1 | 0 | class Base | +| class Derived2 | 0 | class Derived1 | +| class Derived3 | 0 | class Derived1 | +| class Derived4 | 0 | class Derived3 | +| class Derived4 | 1 | class Derived2 | +| class Derived5 | 0 | class Derived1 | +| class Derived6 | 0 | class Derived5 | +| class Derived6 | 1 | class Derived2 | +| class Missing2 | 0 | class Base | +| class Missing3 | 1 | class Base | +| class Wrong1 | 0 | class Derived5 | +| class Wrong1 | 1 | class Derived2 | +| class Wrong2 | 0 | class Derived3 | +| class Wrong2 | 1 | class Derived2 | diff --git a/python/ql/test/library-tests/PointsTo/inheritance/BaseTypes.ql b/python/ql/test/library-tests/PointsTo/inheritance/BaseTypes.ql new file mode 100644 index 000000000000..27b2ed4ce2fe --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/BaseTypes.ql @@ -0,0 +1,7 @@ + +import python + +from ClassObject cls, ClassObject base, int n +where not cls.isBuiltin() and +base = cls.getBaseType(n) +select cls.toString(), n, base.toString() diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Calls.expected b/python/ql/test/library-tests/PointsTo/inheritance/Calls.expected new file mode 100644 index 000000000000..f044ce05f9df --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Calls.expected @@ -0,0 +1,8 @@ +| 9 | Function meth | 3 | +| 14 | Function meth | 8 | +| 22 | Function meth | 13 | +| 27 | Function meth | 8 | +| 27 | Function meth | 13 | +| 32 | Function meth | 26 | +| 38 | Function meth | 13 | +| 43 | Function meth | 13 | diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Calls.ql b/python/ql/test/library-tests/PointsTo/inheritance/Calls.ql new file mode 100644 index 000000000000..d35ac04bb30b --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Calls.ql @@ -0,0 +1,8 @@ + +import python + +from Call c, FunctionObject f + +where f.getACall().getNode() = c + +select c.getLocation().getStartLine(), f.toString(), f.getFunction().getLocation().getStartLine() diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Declared.expected b/python/ql/test/library-tests/PointsTo/inheritance/Declared.expected new file mode 100644 index 000000000000..daf2d029e7fa --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Declared.expected @@ -0,0 +1,11 @@ +| class Base | meth | Function meth | 3 | +| class Derived1 | meth | Function meth | 8 | +| class Derived2 | meth | Function meth | 13 | +| class Derived4 | meth | Function meth | 21 | +| class Derived5 | meth | Function meth | 26 | +| class Derived6 | meth | Function meth | 31 | +| class Missing1 | a | Function a | 49 | +| class Missing2 | b | Function b | 53 | +| class Missing3 | c | Function c | 57 | +| class Wrong1 | meth | Function meth | 37 | +| class Wrong2 | meth | Function meth | 42 | diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Declared.ql b/python/ql/test/library-tests/PointsTo/inheritance/Declared.ql new file mode 100644 index 000000000000..890fe308ea46 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Declared.ql @@ -0,0 +1,7 @@ + +import python +import semmle.python.pointsto.PointsTo + +from ClassObject cls, string name, PyFunctionObject f +where PointsTo::Types::class_declared_attribute(cls, name, f, _, _) +select cls.toString(), name, f.toString(), f.getFunction().getLocation().getStartLine() diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Declares.expected b/python/ql/test/library-tests/PointsTo/inheritance/Declares.expected new file mode 100644 index 000000000000..625172325b69 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Declares.expected @@ -0,0 +1,11 @@ +| Class Base | meth | +| Class Derived1 | meth | +| Class Derived2 | meth | +| Class Derived4 | meth | +| Class Derived5 | meth | +| Class Derived6 | meth | +| Class Missing1 | a | +| Class Missing2 | b | +| Class Missing3 | c | +| Class Wrong1 | meth | +| Class Wrong2 | meth | diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Declares.ql b/python/ql/test/library-tests/PointsTo/inheritance/Declares.ql new file mode 100644 index 000000000000..ee837e66478d --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Declares.ql @@ -0,0 +1,7 @@ + +import python +import semmle.python.pointsto.Base + +from ClassObject cls, string name +where class_declares_attribute(cls, name) +select cls.getPyClass().toString(), name diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Lookup.expected b/python/ql/test/library-tests/PointsTo/inheritance/Lookup.expected new file mode 100644 index 000000000000..d64169b65517 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Lookup.expected @@ -0,0 +1,13 @@ +| class Base | meth | Function meth | 3 | +| class Derived1 | meth | Function meth | 8 | +| class Derived2 | meth | Function meth | 13 | +| class Derived3 | meth | Function meth | 8 | +| class Derived4 | meth | Function meth | 21 | +| class Derived5 | meth | Function meth | 26 | +| class Derived6 | meth | Function meth | 31 | +| class Missing1 | a | Function a | 49 | +| class Missing2 | b | Function b | 53 | +| class Missing2 | meth | Function meth | 3 | +| class Missing3 | c | Function c | 57 | +| class Wrong1 | meth | Function meth | 37 | +| class Wrong2 | meth | Function meth | 42 | diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Lookup.ql b/python/ql/test/library-tests/PointsTo/inheritance/Lookup.ql new file mode 100644 index 000000000000..5f229b8d0d02 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Lookup.ql @@ -0,0 +1,7 @@ + +import python +import semmle.python.pointsto.PointsTo + +from ClassObject cls, string name, PyFunctionObject f +where PointsTo::Types::class_attribute_lookup(cls, name, f, _, _) +select cls.toString(), name, f.toString(), f.getFunction().getLocation().getStartLine() diff --git a/python/ql/test/library-tests/PointsTo/inheritance/MetaClass.expected b/python/ql/test/library-tests/PointsTo/inheritance/MetaClass.expected new file mode 100644 index 000000000000..0348b74c1fb4 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/MetaClass.expected @@ -0,0 +1,9 @@ +| class Base | builtin-class type | +| class Derived1 | builtin-class type | +| class Derived2 | builtin-class type | +| class Derived3 | builtin-class type | +| class Derived4 | builtin-class type | +| class Derived5 | builtin-class type | +| class Derived6 | builtin-class type | +| class Wrong1 | builtin-class type | +| class Wrong2 | builtin-class type | diff --git a/python/ql/test/library-tests/PointsTo/inheritance/MetaClass.ql b/python/ql/test/library-tests/PointsTo/inheritance/MetaClass.ql new file mode 100644 index 000000000000..064cc2ca688c --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/MetaClass.ql @@ -0,0 +1,8 @@ + +import python + +from ClassObject cls, ClassObject meta +where not cls.isBuiltin() and +meta = cls.getMetaClass() +select cls.toString(), meta.toString() + diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Mro.expected b/python/ql/test/library-tests/PointsTo/inheritance/Mro.expected new file mode 100644 index 000000000000..b64915a38a38 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Mro.expected @@ -0,0 +1,12 @@ +| class Base | [Base, object] | +| class Derived1 | [Derived1, Base, object] | +| class Derived2 | [Derived2, Derived1, Base, object] | +| class Derived3 | [Derived3, Derived1, Base, object] | +| class Derived4 | [Derived4, Derived3, Derived2, Derived1, Base, object] | +| class Derived5 | [Derived5, Derived1, Base, object] | +| class Derived6 | [Derived6, Derived5, Derived2, Derived1, Base, object] | +| class Missing1 | [Missing1, UNKNOWN, object] | +| class Missing2 | [Missing2, Base, UNKNOWN, object] | +| class Missing3 | [Missing3, UNKNOWN, Base, object] | +| class Wrong1 | [Wrong1, Derived5, Derived2, Derived1, Base, object] | +| class Wrong2 | [Wrong2, Derived3, Derived2, Derived1, Base, object] | diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Mro.ql b/python/ql/test/library-tests/PointsTo/inheritance/Mro.ql new file mode 100644 index 000000000000..d7373fc2b350 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Mro.ql @@ -0,0 +1,17 @@ + +import python + +/** Make unknown type visible */ +class UnknownType extends ClassObject { + + UnknownType() { this = theUnknownType() } + + override string toString() { result = "*UNKNOWN TYPE" } + + override string getName() { result = "UNKNOWN" } + +} + +from ClassObject c +where not c.isBuiltin() +select c.toString(), c.getMro() diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Self.expected b/python/ql/test/library-tests/PointsTo/inheritance/Self.expected new file mode 100644 index 000000000000..ba9aee6dd7b7 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Self.expected @@ -0,0 +1,18 @@ +| 9 | self | class Derived1 | +| 9 | self | class Derived2 | +| 9 | self | class Derived4 | +| 9 | self | class Derived5 | +| 9 | self | class Derived6 | +| 9 | self | class Wrong1 | +| 9 | self | class Wrong2 | +| 14 | self | class Derived2 | +| 14 | self | class Derived4 | +| 14 | self | class Derived6 | +| 14 | self | class Wrong1 | +| 14 | self | class Wrong2 | +| 22 | self | class Derived4 | +| 27 | self | class Derived5 | +| 27 | self | class Derived6 | +| 32 | self | class Derived6 | +| 38 | self | class Wrong1 | +| 43 | self | class Wrong2 | diff --git a/python/ql/test/library-tests/PointsTo/inheritance/Self.ql b/python/ql/test/library-tests/PointsTo/inheritance/Self.ql new file mode 100644 index 000000000000..a72da5f5248c --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/Self.ql @@ -0,0 +1,6 @@ + +import python + +from NameNode n, Object value, ClassObject cls +where n.getId() = "self" and n.refersTo(value, cls, _) +select n.getNode().getLocation().getStartLine(), value.toString(), cls.toString() diff --git a/python/ql/test/library-tests/PointsTo/inheritance/SuperTypes.expected b/python/ql/test/library-tests/PointsTo/inheritance/SuperTypes.expected new file mode 100644 index 000000000000..48ef4076ed28 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/SuperTypes.expected @@ -0,0 +1,37 @@ +| class Base | builtin-class object | +| class Derived1 | builtin-class object | +| class Derived1 | class Base | +| class Derived2 | builtin-class object | +| class Derived2 | class Base | +| class Derived2 | class Derived1 | +| class Derived3 | builtin-class object | +| class Derived3 | class Base | +| class Derived3 | class Derived1 | +| class Derived4 | builtin-class object | +| class Derived4 | class Base | +| class Derived4 | class Derived1 | +| class Derived4 | class Derived2 | +| class Derived4 | class Derived3 | +| class Derived5 | builtin-class object | +| class Derived5 | class Base | +| class Derived5 | class Derived1 | +| class Derived6 | builtin-class object | +| class Derived6 | class Base | +| class Derived6 | class Derived1 | +| class Derived6 | class Derived2 | +| class Derived6 | class Derived5 | +| class Missing1 | builtin-class object | +| class Missing2 | builtin-class object | +| class Missing2 | class Base | +| class Missing3 | builtin-class object | +| class Missing3 | class Base | +| class Wrong1 | builtin-class object | +| class Wrong1 | class Base | +| class Wrong1 | class Derived1 | +| class Wrong1 | class Derived2 | +| class Wrong1 | class Derived5 | +| class Wrong2 | builtin-class object | +| class Wrong2 | class Base | +| class Wrong2 | class Derived1 | +| class Wrong2 | class Derived2 | +| class Wrong2 | class Derived3 | \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/inheritance/SuperTypes.ql b/python/ql/test/library-tests/PointsTo/inheritance/SuperTypes.ql new file mode 100644 index 000000000000..0793957f2e4e --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/SuperTypes.ql @@ -0,0 +1,7 @@ + +import python + +from ClassObject cls, ClassObject sup +where not cls.isBuiltin() and +sup = cls.getASuperType() +select cls.toString(), sup.toString() diff --git a/python/ql/test/library-tests/PointsTo/inheritance/test.py b/python/ql/test/library-tests/PointsTo/inheritance/test.py new file mode 100644 index 000000000000..58e84dac3067 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/inheritance/test.py @@ -0,0 +1,59 @@ +class Base(object): + + def meth(self): + pass + +class Derived1(Base): + + def meth(self): + return super(Derived1, self).meth() + +class Derived2(Derived1): + + def meth(self): + return super(Derived2, self).meth() + +class Derived3(Derived1): + pass + +class Derived4(Derived3, Derived2): + + def meth(self): + return super(Derived4, self).meth() + +class Derived5(Derived1): + + def meth(self): + return super(Derived5, self).meth() + +class Derived6(Derived5, Derived2): + + def meth(self): + return super(Derived6, self).meth() + +#Incorrect use of super() +class Wrong1(Derived5, Derived2): + + def meth(self): + return super(Derived5, self).meth() + +class Wrong2(Derived3, Derived2): + + def meth(self): + return super(Derived3, self).meth() + +UT = type.__new__(no_name, no_args) +UV = UT() + +class Missing1(UT): + def a(self): + pass + +class Missing2(Base, UT): + def b(self): + pass + +class Missing3(UT, Base): + def c(self): + pass + diff --git a/python/ql/test/library-tests/PointsTo/lookup/Lookup.expected b/python/ql/test/library-tests/PointsTo/lookup/Lookup.expected new file mode 100644 index 000000000000..d01f9d843aa2 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/lookup/Lookup.expected @@ -0,0 +1,79 @@ +| 3 | object | global | +| 8 | self | local | +| 10 | C | global | +| 13 | len | global | +| 13 | sys | global | +| 14 | C | global | +| 16 | D | global | +| 17 | v1 | global | +| 20 | len | global | +| 20 | sys | global | +| 21 | C | global | +| 23 | D | global | +| 24 | v3 | local | +| 27 | arg | local | +| 29 | object | global | +| 30 | classmethod | global | +| 34 | deco | global | +| 38 | f | global | +| 38 | g | global | +| 41 | f | local | +| 43 | object | global | +| 45 | deco | global | +| 49 | v1 | global | +| 50 | v2 | global | +| 51 | v3 | global | +| 52 | v4 | global | +| 53 | list | global | +| 56 | len | global | +| 56 | sys | global | +| 57 | C | global | +| 59 | D | global | +| 60 | args | local | +| 60 | list | global | +| 60 | v5 | local | +| 63 | dict | global | +| 63 | tuple | global | +| 64 | dict | global | +| 66 | dict | global | +| 67 | tuple | global | +| 68 | tuple | global | +| 71 | abstractmethod | global | +| 74 | unknown | global | +| 80 | list | global | +| 80 | unknown | global | +| 80 | x | local | +| 80 | y | non-local | +| 80 | z | non-local | +| 82 | inner | local | +| 87 | list | global | +| 87 | unknown | global | +| 87 | x | non-local | +| 87 | y | non-local | +| 87 | z | non-local | +| 89 | y | local | +| 89 | z | local | +| 90 | inner | local | +| 92 | following | global | +| 98 | a | local | +| 99 | b | local | +| 100 | c | local | +| 103 | BaseException | global | +| 105 | A | local | +| 106 | a | local | +| 111 | z | global | +| 114 | list | global | +| 114 | tuple | global | +| 115 | _tuple | local | +| 116 | _list | local | +| 119 | t | local | +| 120 | d | local | +| 123 | object | global | +| 128 | arg | non-local | +| 128 | args | local | +| 129 | wrapper | local | +| 131 | _internal | local | +| 136 | x | global | +| 140 | object | global | +| 142 | x | global | +| 143 | x | local | \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/lookup/Lookup.ql b/python/ql/test/library-tests/PointsTo/lookup/Lookup.ql new file mode 100644 index 000000000000..43007b7f816f --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/lookup/Lookup.ql @@ -0,0 +1,12 @@ +import python + +from string l, NameNode n +where n.getLocation().getFile().getName().matches("%test.py") and +( + n.isGlobal() and l = "global" + or + n.isLocal() and l = "local" + or + n.isNonLocal() and l = "non-local" +) +select n.getLocation().getStartLine(), n.getId(), l diff --git a/python/ql/test/library-tests/PointsTo/lookup/test.py b/python/ql/test/library-tests/PointsTo/lookup/test.py new file mode 100644 index 000000000000..8b7482308556 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/lookup/test.py @@ -0,0 +1,143 @@ +from __future__ import unicode_literals +import sys +class C(object): + + x = 'C_x' + + def __init__(self): + self.y = 'c_y' + +class D(C): + pass + +if len(sys.argv) > 2: + v1 = C +else: + v1 = D +v2 = v1() + +def f(): + if len(sys.argv) > 3: + v3 = C() + else: + v3 = D() + return v3 + +def g(arg): + return arg + +class X(object): + @classmethod + def method1(cls): + pass + + @deco + def method2(self): + pass + +v4 = g(f()) + +def deco(f): + return f + +class Y(object): + + @deco + def method2(self): + pass + +v1 +v2 +v3 +v4 +list + +def h(args): + if len(sys.argv) > 4: + v5 = C() + else: + v5 = D() + return v5, list(args) + +def j(): + return tuple, dict +dict +dict = 7 +dict +tuple = tuple +tuple + +from abc import abstractmethod +abstractmethod + +from module import unknown +unknown + +#Value of variables in inner functions +def outer(): + y = 1 + def inner(x): + return x + y + z + unknown + list + z = 2; + return inner + +def outer_use_vars(x): + y = 1 + def inner(): + return x + y + z + unknown + list + z = 2; + y + z + return inner + +y = lambda x : following() + +def following(): + pass + +def params_and_defaults(a, b={}, c = 1): + a + b + c + +def inner_cls(): + class A(BaseException): + pass + a = A() + raise a + + + + +z + +def multiple_assignment(): + _tuple, _list = tuple, list + _tuple + _list + +def vararg_kwarg(*t, **d): + t + d + + +class E(object): + + def _internal(arg): + # arg is not a C + def wrapper(args): + return arg(args) + return wrapper + + @_internal + def method(self, *args): + pass + +x = 1 +x + + +#Global in class scope +class F(object): + + x = x + x diff --git a/python/ql/test/library-tests/PointsTo/metaclass/test.expected b/python/ql/test/library-tests/PointsTo/metaclass/test.expected new file mode 100644 index 000000000000..b34df4189bd9 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/metaclass/test.expected @@ -0,0 +1,7 @@ +| class C1 | class Meta1 | +| class C2 | class Meta2 | +| class C3 | builtin-class type | +| class C4 | class Meta2 | +| class Meta1 | builtin-class type | +| class Meta2 | builtin-class type | +| class NewStyleEvenForPython2 | builtin-class type | diff --git a/python/ql/test/library-tests/PointsTo/metaclass/test.py b/python/ql/test/library-tests/PointsTo/metaclass/test.py new file mode 100644 index 000000000000..c09b3546ecb5 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/metaclass/test.py @@ -0,0 +1,36 @@ +import six + +class Meta1(type): + pass + +class Meta2(type): + pass + +@six.add_metaclass(Meta1) +class C1(object): + pass + +@six.add_metaclass(Meta2) +class C2(object): + pass + +#No explicit metaclass +class C3(object): + pass + +#Multiple non-conflicting metaclasses: +class C4(C2, object): + pass + +#Metaclass conflict +class C5(C2, C1): + pass + +@not_known_decorator +class C6(object): + pass + +__metaclass__ = type + +class NewStyleEvenForPython2: + pass diff --git a/python/ql/test/library-tests/PointsTo/metaclass/test.ql b/python/ql/test/library-tests/PointsTo/metaclass/test.ql new file mode 100644 index 000000000000..c1df0b003c55 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/metaclass/test.ql @@ -0,0 +1,6 @@ + +import python + +from ClassObject cls +where cls.getPyClass().getEnclosingModule().getName() = "test" +select cls.toString(), cls.getMetaClass().toString() diff --git a/python/ql/test/library-tests/PointsTo/new/Call.expected b/python/ql/test/library-tests/PointsTo/new/Call.expected new file mode 100644 index 000000000000..b97734a83020 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Call.expected @@ -0,0 +1,28 @@ +| b_condition.py:36 | ControlFlowNode for isinstance() | isinstance | +| b_condition.py:82 | ControlFlowNode for callable() | callable | +| b_condition.py:102 | ControlFlowNode for isinstance() | isinstance | +| d_globals.py:29 | ControlFlowNode for init() | init | +| d_globals.py:77 | ControlFlowNode for set_g4() | set_g4 | +| d_globals.py:81 | ControlFlowNode for set_g4_indirect() | set_g4_indirect | +| d_globals.py:104 | ControlFlowNode for inner() | outer.inner | +| d_globals.py:128 | ControlFlowNode for Attribute() | list.append | +| g_class_init.py:6 | ControlFlowNode for Attribute() | C._init | +| g_class_init.py:11 | ControlFlowNode for Attribute() | C._init2 | +| g_class_init.py:18 | ControlFlowNode for isinstance() | isinstance | +| g_class_init.py:36 | ControlFlowNode for Attribute() | object.__init__ | +| l_calls.py:4 | ControlFlowNode for Attribute() | list.append | +| l_calls.py:7 | ControlFlowNode for len() | len | +| l_calls.py:9 | ControlFlowNode for foo() | foo | +| l_calls.py:10 | ControlFlowNode for bar() | bar | +| l_calls.py:24 | ControlFlowNode for Attribute() | Owner.cm | +| l_calls.py:25 | ControlFlowNode for Attribute() | Owner.cm2 | +| q_super.py:4 | ControlFlowNode for Attribute() | object.__init__ | +| q_super.py:12 | ControlFlowNode for Attribute() | Base2.__init__ | +| q_super.py:22 | ControlFlowNode for Attribute() | Base1.meth | +| q_super.py:27 | ControlFlowNode for Attribute() | Derived1.meth | +| q_super.py:32 | ControlFlowNode for Attribute() | Derived1.meth | +| q_super.py:38 | ControlFlowNode for Attribute() | Derived2.meth | +| q_super.py:52 | ControlFlowNode for Attribute() | DA.__init__ | +| q_super.py:59 | ControlFlowNode for Attribute() | DA.__init__ | +| q_super.py:66 | ControlFlowNode for Attribute() | DA.__init__ | +| q_super.py:76 | ControlFlowNode for i() | object.__init__ | diff --git a/python/ql/test/library-tests/PointsTo/new/Call.ql b/python/ql/test/library-tests/PointsTo/new/Call.ql new file mode 100644 index 000000000000..f740b0060f69 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Call.ql @@ -0,0 +1,8 @@ + +import python +import Util + +from ControlFlowNode call, FunctionObject func + +where call = func.getACall() +select locate(call.getLocation(), "abdglq"), call.toString(), func.getQualifiedName() \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/new/ClassMethod.expected b/python/ql/test/library-tests/PointsTo/new/ClassMethod.expected new file mode 100644 index 000000000000..f93d242b1e4f --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/ClassMethod.expected @@ -0,0 +1,2 @@ +| l_calls.py:24 | Function cm | code/l_calls.py:14 | +| l_calls.py:25 | Function cm2 | code/l_calls.py:18 | diff --git a/python/ql/test/library-tests/PointsTo/new/ClassMethod.ql b/python/ql/test/library-tests/PointsTo/new/ClassMethod.ql new file mode 100644 index 000000000000..2d13f2ae851c --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/ClassMethod.ql @@ -0,0 +1,9 @@ + +import python +import semmle.python.types.Descriptors +import Util + +from ClassMethodObject cm, CallNode call +where call = cm.getACall() +select locate(call.getLocation(), "lp"), cm.getFunction().toString(), cm.(ControlFlowNode).getLocation().toString() + diff --git a/python/ql/test/library-tests/PointsTo/new/Dataflow.expected b/python/ql/test/library-tests/PointsTo/new/Dataflow.expected new file mode 100644 index 000000000000..5ea3736af763 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Dataflow.expected @@ -0,0 +1,753 @@ +| __init__.py:0 | *_0 = ScopeEntryDefinition | +| __init__.py:0 | __name___0 = ScopeEntryDefinition | +| __init__.py:0 | __package___0 = ScopeEntryDefinition | +| __init__.py:0 | module2_0 = ImplicitSubModuleDefinition | +| __init__.py:0 | moduleX_0 = ImplicitSubModuleDefinition | +| __init__.py:0 | sys_0 = ScopeEntryDefinition | +| __init__.py:1 | *_1 = ImportStarRefinement(*_0) | +| __init__.py:1 | __name___1 = ImportStarRefinement(__name___0) | +| __init__.py:1 | __package___1 = ImportStarRefinement(__package___0) | +| __init__.py:1 | sys_1 = ImportStarRefinement(sys_0) | +| __init__.py:2 | *_2 = ImportStarRefinement(*_1) | +| __init__.py:2 | __name___2 = ImportStarRefinement(__name___1) | +| __init__.py:2 | __package___2 = ImportStarRefinement(__package___1) | +| __init__.py:2 | module_0 = ImportMember | +| __init__.py:3 | sys_2 = ImportExpr | +| __init__.py:4 | module3_0 = ImportMember | +| __init__.py:5 | module2_1 = IntegerLiteral | +| __init__.py:6 | module4_0 = ImportMember | +| __init__.py:7 | module5_0 = ImportMember | +| __init__.py:8 | moduleX_1 = ImportMember | +| a_simple.py:0 | __name___0 = ScopeEntryDefinition | +| a_simple.py:0 | __package___0 = ScopeEntryDefinition | +| a_simple.py:2 | f1_0 = FloatLiteral | +| a_simple.py:5 | i1_0 = IntegerLiteral | +| a_simple.py:6 | s_0 = Tuple | +| a_simple.py:8 | func_0 = FunctionExpr | +| a_simple.py:11 | C_0 = ClassExpr | +| a_simple.py:14 | d_0 = ParameterDefinition | +| a_simple.py:14 | t_0 = ParameterDefinition | +| a_simple.py:14 | vararg_kwarg_0 = FunctionExpr | +| a_simple.py:18 | multi_loop_0 = FunctionExpr | +| a_simple.py:18 | seq_0 = ParameterDefinition | +| a_simple.py:18 | y_0 = ScopeEntryDefinition | +| a_simple.py:19 | x_0 = None | +| a_simple.py:20 | x_1 = phi(x_0, x_2) | +| a_simple.py:20 | x_2 = ... | +| a_simple.py:20 | y_1 = phi(y_0, y_2) | +| a_simple.py:20 | y_2 = ... | +| a_simple.py:23 | with_definition_0 = FunctionExpr | +| a_simple.py:23 | x_0 = ParameterDefinition | +| a_simple.py:24 | y_0 = with | +| a_simple.py:27 | multi_loop_in_try_0 = FunctionExpr | +| a_simple.py:27 | p_0 = ScopeEntryDefinition | +| a_simple.py:27 | q_0 = ScopeEntryDefinition | +| a_simple.py:27 | x_0 = ParameterDefinition | +| a_simple.py:29 | p_1 = phi(p_0, p_2) | +| a_simple.py:29 | p_2 = ... | +| a_simple.py:29 | q_1 = phi(q_0, q_2) | +| a_simple.py:29 | q_2 = ... | +| a_simple.py:34 | args_0 = ParameterDefinition | +| a_simple.py:34 | f_0 = FunctionExpr | +| a_simple.py:34 | kwargs_0 = ParameterDefinition | +| b_condition.py:0 | __name___0 = ScopeEntryDefinition | +| b_condition.py:0 | __package___0 = ScopeEntryDefinition | +| b_condition.py:0 | double_attr_check_0 = ScopeEntryDefinition | +| b_condition.py:0 | g_0 = ScopeEntryDefinition | +| b_condition.py:0 | h_0 = ScopeEntryDefinition | +| b_condition.py:0 | k_0 = ScopeEntryDefinition | +| b_condition.py:0 | loop_0 = ScopeEntryDefinition | +| b_condition.py:0 | not_or_not_0 = ScopeEntryDefinition | +| b_condition.py:0 | odasa6261_0 = ScopeEntryDefinition | +| b_condition.py:0 | split_bool1_0 = ScopeEntryDefinition | +| b_condition.py:0 | v2_0 = ScopeEntryDefinition | +| b_condition.py:4 | f_0 = FunctionExpr | +| b_condition.py:4 | y_0 = ParameterDefinition | +| b_condition.py:5 | x_0 = IfExp | +| b_condition.py:8 | x_1 = IntegerLiteral | +| b_condition.py:9 | x_2 = Pi(x_0) [false] | +| b_condition.py:9 | x_3 = phi(x_1, x_2) | +| b_condition.py:11 | x_4 = IfExp | +| b_condition.py:14 | x_5 = IntegerLiteral | +| b_condition.py:15 | x_6 = Pi(x_4) [false] | +| b_condition.py:15 | x_7 = phi(x_5, x_6) | +| b_condition.py:17 | x_8 = IfExp | +| b_condition.py:20 | x_9 = None | +| b_condition.py:21 | x_10 = Pi(x_8) [false] | +| b_condition.py:21 | x_11 = phi(x_9, x_10) | +| b_condition.py:23 | x_12 = IfExp | +| b_condition.py:25 | x_13 = Pi(x_12) [true] | +| b_condition.py:25 | x_14 = IfExp | +| b_condition.py:26 | x_15 = ArgumentRefinement(x_14) | +| b_condition.py:28 | x_16 = IntegerLiteral | +| b_condition.py:29 | x_17 = phi(x_15, x_16) | +| b_condition.py:31 | x_18 = IfExp | +| b_condition.py:33 | x_19 = IntegerLiteral | +| b_condition.py:34 | x_20 = Pi(x_18) [false] | +| b_condition.py:34 | x_21 = phi(x_19, x_20) | +| b_condition.py:34 | x_22 = ArgumentRefinement(x_21) | +| b_condition.py:36 | x_23 = ArgumentRefinement(x_22) | +| b_condition.py:36 | x_24 = Pi(x_23) [true] | +| b_condition.py:37 | x_25 = ArgumentRefinement(x_24) | +| b_condition.py:39 | v2_1 = thing() | +| b_condition.py:41 | v2_2 = AttributeAssignment 'x'(v2_1) | +| b_condition.py:43 | v2_3 = Pi(v2_2) [true] | +| b_condition.py:47 | v2_4 = Pi(v2_2) [false] | +| b_condition.py:47 | v2_5 = phi(v2_3, v2_4) | +| b_condition.py:50 | g_1 = FunctionExpr | +| b_condition.py:50 | x_0 = ParameterDefinition | +| b_condition.py:50 | x_2 = Pi(x_0) [false] | +| b_condition.py:50 | x_3 = phi(x_1, x_2) | +| b_condition.py:52 | x_1 = Pi(x_0) [true] | +| b_condition.py:55 | loop_1 = FunctionExpr | +| b_condition.py:55 | seq_0 = ParameterDefinition | +| b_condition.py:55 | v_0 = ScopeEntryDefinition | +| b_condition.py:56 | v_1 = Pi(v_3) [false] | +| b_condition.py:56 | v_2 = phi(v_0, v_1, v_5) | +| b_condition.py:56 | v_3 = IterationDefinition | +| b_condition.py:58 | v_4 = Pi(v_3) [true] | +| b_condition.py:58 | v_5 = ArgumentRefinement(v_4) | +| b_condition.py:61 | double_attr_check_1 = FunctionExpr | +| b_condition.py:61 | x_0 = ParameterDefinition | +| b_condition.py:61 | x_5 = Pi(x_2) [false] | +| b_condition.py:61 | x_5 = Pi(x_3) [false] | +| b_condition.py:61 | x_7 = phi(x_1, x_4) | +| b_condition.py:61 | x_7 = phi(x_2, x_5) | +| b_condition.py:61 | y_0 = ParameterDefinition | +| b_condition.py:61 | y_2 = Pi(y_0) [false] | +| b_condition.py:61 | y_3 = phi(y_0, y_1) | +| b_condition.py:61 | y_3 = phi(y_1, y_2) | +| b_condition.py:63 | x_1 = Pi(x_0) [true] | +| b_condition.py:64 | x_2 = Pi(x_0) [false] | +| b_condition.py:65 | y_1 = Pi(y_0) [true] | +| b_condition.py:66 | x_3 = Pi(x_2) [true] | +| b_condition.py:67 | x_4 = Pi(x_3) [true] | +| b_condition.py:69 | h_1 = FunctionExpr | +| b_condition.py:70 | b_0 = IfExp | +| b_condition.py:72 | b_1 = IntegerLiteral | +| b_condition.py:73 | b_2 = Pi(b_0) [false] | +| b_condition.py:73 | b_3 = phi(b_1, b_2) | +| b_condition.py:75 | k_1 = FunctionExpr | +| b_condition.py:76 | t_0 = type | +| b_condition.py:78 | t_1 = object | +| b_condition.py:79 | t_2 = Pi(t_0) [false] | +| b_condition.py:79 | t_3 = phi(t_1, t_2) | +| b_condition.py:79 | t_4 = ArgumentRefinement(t_3) | +| b_condition.py:81 | bar_0 = ScopeEntryDefinition | +| b_condition.py:81 | bar_2 = phi(bar_0, bar_1) | +| b_condition.py:81 | foo_0 = ParameterDefinition | +| b_condition.py:81 | foo_4 = Pi(foo_1) [false] | +| b_condition.py:81 | foo_5 = phi(foo_2, foo_4) | +| b_condition.py:81 | odasa6261_1 = FunctionExpr | +| b_condition.py:82 | foo_1 = ArgumentRefinement(foo_0) | +| b_condition.py:83 | bar_1 = FunctionExpr | +| b_condition.py:83 | foo_2 = Pi(foo_1) [true] | +| b_condition.py:83 | foo_3 = ScopeEntryDefinition | +| b_condition.py:87 | split_bool1_1 = FunctionExpr | +| b_condition.py:87 | x_0 = ParameterDefinition | +| b_condition.py:87 | y_0 = ParameterDefinition | +| b_condition.py:88 | x_1 = Pi(x_0) [true] | +| b_condition.py:90 | x_4 = Pi(x_0) [false] | +| b_condition.py:90 | x_5 = SingleSuccessorGuard(x_4) [false] | +| b_condition.py:90 | x_6 = SingleSuccessorGuard(x_1) [false] | +| b_condition.py:90 | y_1 = Pi(y_0) [true] | +| b_condition.py:90 | y_4 = Pi(y_0) [false] | +| b_condition.py:92 | x_2 = SingleSuccessorGuard(x_5) [false] | +| b_condition.py:92 | x_7 = SingleSuccessorGuard(x_6) [true] | +| b_condition.py:93 | y_5 = ArgumentRefinement(y_4) | +| b_condition.py:95 | y_2 = ArgumentRefinement(y_1) | +| b_condition.py:96 | y_3 = SingleSuccessorGuard(y_2) [true] | +| b_condition.py:96 | y_6 = SingleSuccessorGuard(y_5) [false] | +| b_condition.py:97 | x_3 = ArgumentRefinement(x_2) | +| b_condition.py:99 | x_8 = ArgumentRefinement(x_7) | +| b_condition.py:101 | a_0 = ParameterDefinition | +| b_condition.py:101 | not_or_not_1 = FunctionExpr | +| b_condition.py:102 | a_1 = ArgumentRefinement(a_0) | +| b_condition.py:104 | a_2 = Pi(a_1) [false] | +| b_condition.py:105 | a_3 = Pi(a_2) [false] | +| b_condition.py:107 | a_4 = Pi(a_3) [false] | +| d_globals.py:0 | D_0 = ScopeEntryDefinition | +| d_globals.py:0 | Ugly_0 = ScopeEntryDefinition | +| d_globals.py:0 | X_0 = ScopeEntryDefinition | +| d_globals.py:0 | __name___0 = ScopeEntryDefinition | +| d_globals.py:0 | __package___0 = ScopeEntryDefinition | +| d_globals.py:0 | dict_0 = ScopeEntryDefinition | +| d_globals.py:0 | g3_0 = ScopeEntryDefinition | +| d_globals.py:0 | g4_0 = ScopeEntryDefinition | +| d_globals.py:0 | get_g4_0 = ScopeEntryDefinition | +| d_globals.py:0 | glob_0 = ScopeEntryDefinition | +| d_globals.py:0 | k_0 = ScopeEntryDefinition | +| d_globals.py:0 | modinit_0 = ScopeEntryDefinition | +| d_globals.py:0 | outer_0 = ScopeEntryDefinition | +| d_globals.py:0 | redefine_0 = ScopeEntryDefinition | +| d_globals.py:0 | set_g4_0 = ScopeEntryDefinition | +| d_globals.py:0 | set_g4_indirect_0 = ScopeEntryDefinition | +| d_globals.py:0 | tuple_0 = ScopeEntryDefinition | +| d_globals.py:0 | use_list_attribute_0 = ScopeEntryDefinition | +| d_globals.py:0 | x_0 = ScopeEntryDefinition | +| d_globals.py:0 | y_0 = ScopeEntryDefinition | +| d_globals.py:0 | z_0 = ScopeEntryDefinition | +| d_globals.py:2 | dict_2 = ScopeEntryDefinition | +| d_globals.py:2 | g1_2 = ScopeEntryDefinition | +| d_globals.py:2 | g2_2 = ScopeEntryDefinition | +| d_globals.py:2 | g3_2 = ScopeEntryDefinition | +| d_globals.py:2 | g4_1 = ScopeEntryDefinition | +| d_globals.py:2 | glob_2 = ScopeEntryDefinition | +| d_globals.py:2 | j_0 = FunctionExpr | +| d_globals.py:2 | tuple_2 = ScopeEntryDefinition | +| d_globals.py:2 | z_2 = ScopeEntryDefinition | +| d_globals.py:5 | dict_1 = IntegerLiteral | +| d_globals.py:7 | tuple_1 = tuple | +| d_globals.py:14 | g1_0 = None | +| d_globals.py:16 | assign_global_0 = FunctionExpr | +| d_globals.py:16 | g2_3 = ScopeEntryDefinition | +| d_globals.py:16 | g3_3 = ScopeEntryDefinition | +| d_globals.py:16 | g4_2 = ScopeEntryDefinition | +| d_globals.py:16 | glob_3 = ScopeEntryDefinition | +| d_globals.py:16 | z_3 = ScopeEntryDefinition | +| d_globals.py:18 | g1_3 = IntegerLiteral | +| d_globals.py:23 | g2_0 = None | +| d_globals.py:25 | g1_4 = ScopeEntryDefinition | +| d_globals.py:25 | g3_4 = ScopeEntryDefinition | +| d_globals.py:25 | g4_3 = ScopeEntryDefinition | +| d_globals.py:25 | glob_4 = ScopeEntryDefinition | +| d_globals.py:25 | init_0 = FunctionExpr | +| d_globals.py:25 | z_4 = ScopeEntryDefinition | +| d_globals.py:27 | g2_4 = IntegerLiteral | +| d_globals.py:29 | g1_1 = CallsiteRefinement(g1_0) | +| d_globals.py:29 | g2_1 = CallsiteRefinement(g2_0) | +| d_globals.py:29 | glob_1 = CallsiteRefinement(glob_0) | +| d_globals.py:29 | z_1 = CallsiteRefinement(z_0) | +| d_globals.py:33 | g3_1 = None | +| d_globals.py:35 | Ugly_1 = ClassExpr | +| d_globals.py:37 | __init___0 = FunctionExpr | +| d_globals.py:37 | g1_5 = ScopeEntryDefinition | +| d_globals.py:37 | g2_5 = ScopeEntryDefinition | +| d_globals.py:37 | g4_4 = ScopeEntryDefinition | +| d_globals.py:37 | glob_5 = ScopeEntryDefinition | +| d_globals.py:37 | self_0 = ParameterDefinition | +| d_globals.py:37 | z_5 = ScopeEntryDefinition | +| d_globals.py:39 | g3_5 = IntegerLiteral | +| d_globals.py:41 | g1_6 = ScopeEntryDefinition | +| d_globals.py:41 | g2_6 = ScopeEntryDefinition | +| d_globals.py:41 | g3_6 = ScopeEntryDefinition | +| d_globals.py:41 | g4_5 = ScopeEntryDefinition | +| d_globals.py:41 | glob_6 = ScopeEntryDefinition | +| d_globals.py:41 | meth_0 = FunctionExpr | +| d_globals.py:41 | self_0 = ParameterDefinition | +| d_globals.py:41 | z_6 = ScopeEntryDefinition | +| d_globals.py:46 | x_1 = IntegerLiteral | +| d_globals.py:49 | x_2 = IntegerLiteral | +| d_globals.py:51 | x_3 = phi(x_1, x_2) | +| d_globals.py:52 | y_1 = IntegerLiteral | +| d_globals.py:54 | y_2 = IntegerLiteral | +| d_globals.py:59 | y_3 = phi(y_1, y_2) | +| d_globals.py:62 | X_1 = ClassExpr | +| d_globals.py:62 | X_2 = ScopeEntryDefinition | +| d_globals.py:62 | g3_7 = ScopeEntryDefinition | +| d_globals.py:62 | y_0 = ScopeEntryDefinition | +| d_globals.py:62 | y_4 = ScopeEntryDefinition | +| d_globals.py:63 | y_1 = y | +| d_globals.py:64 | v4_0 = v3 | +| d_globals.py:70 | arg_0 = ParameterDefinition | +| d_globals.py:70 | g1_7 = ScopeEntryDefinition | +| d_globals.py:70 | g2_7 = ScopeEntryDefinition | +| d_globals.py:70 | g3_8 = ScopeEntryDefinition | +| d_globals.py:70 | g4_7 = ScopeEntryDefinition | +| d_globals.py:70 | glob_7 = ScopeEntryDefinition | +| d_globals.py:70 | k_1 = FunctionExpr | +| d_globals.py:70 | z_7 = ScopeEntryDefinition | +| d_globals.py:73 | g4_6 = None | +| d_globals.py:75 | g1_8 = ScopeEntryDefinition | +| d_globals.py:75 | g2_8 = ScopeEntryDefinition | +| d_globals.py:75 | g3_9 = ScopeEntryDefinition | +| d_globals.py:75 | g4_8 = ScopeEntryDefinition | +| d_globals.py:75 | get_g4_1 = FunctionExpr | +| d_globals.py:75 | glob_8 = ScopeEntryDefinition | +| d_globals.py:75 | set_g4_2 = ScopeEntryDefinition | +| d_globals.py:75 | z_8 = ScopeEntryDefinition | +| d_globals.py:77 | g1_9 = CallsiteRefinement(g1_8) | +| d_globals.py:77 | g2_9 = CallsiteRefinement(g2_8) | +| d_globals.py:77 | g3_10 = CallsiteRefinement(g3_9) | +| d_globals.py:77 | g4_9 = Pi(g4_8) [true] | +| d_globals.py:77 | g4_10 = CallsiteRefinement(g4_9) | +| d_globals.py:77 | glob_9 = CallsiteRefinement(glob_8) | +| d_globals.py:77 | z_9 = CallsiteRefinement(z_8) | +| d_globals.py:78 | g1_10 = phi(g1_8, g1_9) | +| d_globals.py:78 | g2_10 = phi(g2_8, g2_9) | +| d_globals.py:78 | g3_11 = phi(g3_9, g3_10) | +| d_globals.py:78 | g4_11 = Pi(g4_8) [false] | +| d_globals.py:78 | g4_12 = phi(g4_10, g4_11) | +| d_globals.py:78 | glob_10 = phi(glob_8, glob_9) | +| d_globals.py:78 | z_10 = phi(z_8, z_9) | +| d_globals.py:80 | g1_11 = ScopeEntryDefinition | +| d_globals.py:80 | g2_11 = ScopeEntryDefinition | +| d_globals.py:80 | g3_12 = ScopeEntryDefinition | +| d_globals.py:80 | g4_13 = ScopeEntryDefinition | +| d_globals.py:80 | glob_11 = ScopeEntryDefinition | +| d_globals.py:80 | set_g4_1 = FunctionExpr | +| d_globals.py:80 | set_g4_indirect_2 = ScopeEntryDefinition | +| d_globals.py:80 | z_11 = ScopeEntryDefinition | +| d_globals.py:81 | g1_12 = CallsiteRefinement(g1_11) | +| d_globals.py:81 | g2_12 = CallsiteRefinement(g2_11) | +| d_globals.py:81 | g3_13 = CallsiteRefinement(g3_12) | +| d_globals.py:81 | g4_14 = CallsiteRefinement(g4_13) | +| d_globals.py:81 | glob_12 = CallsiteRefinement(glob_11) | +| d_globals.py:81 | z_12 = CallsiteRefinement(z_11) | +| d_globals.py:83 | g1_13 = ScopeEntryDefinition | +| d_globals.py:83 | g2_13 = ScopeEntryDefinition | +| d_globals.py:83 | g3_14 = ScopeEntryDefinition | +| d_globals.py:83 | glob_13 = ScopeEntryDefinition | +| d_globals.py:83 | set_g4_indirect_1 = FunctionExpr | +| d_globals.py:83 | z_13 = ScopeEntryDefinition | +| d_globals.py:85 | g4_15 = False | +| d_globals.py:87 | modinit_1 = ClassExpr | +| d_globals.py:92 | modinit_2 = DeletionDefinition | +| d_globals.py:95 | g1_14 = ScopeEntryDefinition | +| d_globals.py:95 | g2_14 = ScopeEntryDefinition | +| d_globals.py:95 | g3_15 = ScopeEntryDefinition | +| d_globals.py:95 | g4_16 = ScopeEntryDefinition | +| d_globals.py:95 | glob_14 = ScopeEntryDefinition | +| d_globals.py:95 | outer_1 = FunctionExpr | +| d_globals.py:95 | z_14 = ScopeEntryDefinition | +| d_globals.py:96 | g1_16 = ScopeEntryDefinition | +| d_globals.py:96 | g2_16 = ScopeEntryDefinition | +| d_globals.py:96 | g3_17 = ScopeEntryDefinition | +| d_globals.py:96 | g4_18 = ScopeEntryDefinition | +| d_globals.py:96 | inner_0 = FunctionExpr | +| d_globals.py:96 | z_16 = ScopeEntryDefinition | +| d_globals.py:98 | glob_16 = IntegerLiteral | +| d_globals.py:101 | g1_17 = ScopeEntryDefinition | +| d_globals.py:101 | g2_17 = ScopeEntryDefinition | +| d_globals.py:101 | g3_18 = ScopeEntryDefinition | +| d_globals.py:101 | g4_19 = ScopeEntryDefinition | +| d_globals.py:101 | glob_17 = ScopeEntryDefinition | +| d_globals.py:101 | otherInner_0 = FunctionExpr | +| d_globals.py:101 | z_17 = ScopeEntryDefinition | +| d_globals.py:104 | g1_15 = CallsiteRefinement(g1_14) | +| d_globals.py:104 | g2_15 = CallsiteRefinement(g2_14) | +| d_globals.py:104 | g3_16 = CallsiteRefinement(g3_15) | +| d_globals.py:104 | g4_17 = CallsiteRefinement(g4_16) | +| d_globals.py:104 | glob_15 = CallsiteRefinement(glob_14) | +| d_globals.py:104 | z_15 = CallsiteRefinement(z_14) | +| d_globals.py:107 | g1_18 = ScopeEntryDefinition | +| d_globals.py:107 | g2_18 = ScopeEntryDefinition | +| d_globals.py:107 | g3_19 = ScopeEntryDefinition | +| d_globals.py:107 | g4_20 = ScopeEntryDefinition | +| d_globals.py:107 | glob_18 = ScopeEntryDefinition | +| d_globals.py:107 | redefine_1 = FunctionExpr | +| d_globals.py:107 | z_18 = ScopeEntryDefinition | +| d_globals.py:110 | z_19 = IntegerLiteral | +| d_globals.py:113 | glob_19 = IntegerLiteral | +| d_globals.py:118 | D_1 = ClassExpr | +| d_globals.py:120 | __init___0 = FunctionExpr | +| d_globals.py:120 | g1_19 = ScopeEntryDefinition | +| d_globals.py:120 | g2_19 = ScopeEntryDefinition | +| d_globals.py:120 | g3_20 = ScopeEntryDefinition | +| d_globals.py:120 | g4_21 = ScopeEntryDefinition | +| d_globals.py:120 | glob_20 = ScopeEntryDefinition | +| d_globals.py:120 | self_0 = ParameterDefinition | +| d_globals.py:120 | z_20 = ScopeEntryDefinition | +| d_globals.py:123 | dict_3 = ScopeEntryDefinition | +| d_globals.py:123 | foo_0 = FunctionExpr | +| d_globals.py:123 | g1_20 = ScopeEntryDefinition | +| d_globals.py:123 | g2_20 = ScopeEntryDefinition | +| d_globals.py:123 | g3_21 = ScopeEntryDefinition | +| d_globals.py:123 | g4_22 = ScopeEntryDefinition | +| d_globals.py:123 | glob_21 = ScopeEntryDefinition | +| d_globals.py:123 | self_0 = ParameterDefinition | +| d_globals.py:123 | z_21 = ScopeEntryDefinition | +| d_globals.py:126 | g1_21 = ScopeEntryDefinition | +| d_globals.py:126 | g2_21 = ScopeEntryDefinition | +| d_globals.py:126 | g3_22 = ScopeEntryDefinition | +| d_globals.py:126 | g4_23 = ScopeEntryDefinition | +| d_globals.py:126 | glob_22 = ScopeEntryDefinition | +| d_globals.py:126 | use_list_attribute_1 = FunctionExpr | +| d_globals.py:126 | z_22 = ScopeEntryDefinition | +| d_globals.py:127 | l_0 = List | +| d_globals.py:128 | g1_22 = CallsiteRefinement(g1_21) | +| d_globals.py:128 | g2_22 = CallsiteRefinement(g2_21) | +| d_globals.py:128 | g3_23 = CallsiteRefinement(g3_22) | +| d_globals.py:128 | g4_24 = CallsiteRefinement(g4_23) | +| d_globals.py:128 | glob_23 = CallsiteRefinement(glob_22) | +| d_globals.py:128 | l_1 = ArgumentRefinement(l_0) | +| d_globals.py:128 | z_23 = CallsiteRefinement(z_22) | +| e_temporal.py:0 | __name___0 = ScopeEntryDefinition | +| e_temporal.py:0 | __package___0 = ScopeEntryDefinition | +| e_temporal.py:0 | x_0 = ScopeEntryDefinition | +| e_temporal.py:2 | sys_0 = ImportExpr | +| e_temporal.py:4 | f_0 = FunctionExpr | +| e_temporal.py:4 | sys_1 = ScopeEntryDefinition | +| e_temporal.py:9 | arg_0 = ParameterDefinition | +| e_temporal.py:9 | g_0 = FunctionExpr | +| e_temporal.py:12 | x_1 = g() | +| f_finally.py:0 | __name___0 = ScopeEntryDefinition | +| f_finally.py:0 | __package___0 = ScopeEntryDefinition | +| f_finally.py:1 | Queue_0 = ClassExpr | +| f_finally.py:3 | close_0 = FunctionExpr | +| f_finally.py:3 | close_4 = Pi(close_0) [false] | +| f_finally.py:3 | close_5 = phi(close_3, close_4) | +| f_finally.py:3 | self_0 = ParameterDefinition | +| f_finally.py:3 | self_3 = phi(self_1, self_2) | +| f_finally.py:4 | self_1 = AttributeAssignment '_closed'(self_0) | +| f_finally.py:8 | close_0 = Attribute | +| f_finally.py:8 | close_1 = Attribute | +| f_finally.py:9 | close_2 = SingleSuccessorGuard(close_1) [true] | +| f_finally.py:10 | close_3 = Pi(close_0) [true] | +| f_finally.py:10 | self_2 = AttributeAssignment '_close'(self_1) | +| g_class_init.py:0 | __name___0 = ScopeEntryDefinition | +| g_class_init.py:0 | __package___0 = ScopeEntryDefinition | +| g_class_init.py:3 | C_0 = ClassExpr | +| g_class_init.py:5 | __init___0 = FunctionExpr | +| g_class_init.py:5 | self_0 = ParameterDefinition | +| g_class_init.py:6 | self_1 = SelfCallsiteRefinement(self_0) | +| g_class_init.py:7 | self_2 = AttributeAssignment 'x'(self_1) | +| g_class_init.py:9 | _init_0 = FunctionExpr | +| g_class_init.py:9 | self_0 = ParameterDefinition | +| g_class_init.py:10 | self_1 = AttributeAssignment 'y'(self_0) | +| g_class_init.py:11 | self_2 = SelfCallsiteRefinement(self_1) | +| g_class_init.py:13 | _init2_0 = FunctionExpr | +| g_class_init.py:13 | self_0 = ParameterDefinition | +| g_class_init.py:14 | self_1 = AttributeAssignment 'z'(self_0) | +| g_class_init.py:16 | method_0 = FunctionExpr | +| g_class_init.py:16 | self_0 = ParameterDefinition | +| g_class_init.py:19 | self_1 = Pi(self_0) [true] | +| g_class_init.py:20 | self_2 = Pi(self_0) [false] | +| g_class_init.py:20 | self_3 = phi(self_1, self_2) | +| g_class_init.py:24 | Oddities_0 = ClassExpr | +| g_class_init.py:24 | float_0 = ScopeEntryDefinition | +| g_class_init.py:24 | int_0 = ScopeEntryDefinition | +| g_class_init.py:26 | int_1 = int | +| g_class_init.py:27 | float_1 = float | +| g_class_init.py:28 | l_0 = len | +| g_class_init.py:29 | h_0 = hash | +| g_class_init.py:32 | D_0 = ClassExpr | +| g_class_init.py:34 | D_1 = ScopeEntryDefinition | +| g_class_init.py:34 | __init___0 = FunctionExpr | +| g_class_init.py:34 | self_0 = ParameterDefinition | +| g_class_init.py:35 | D_2 = ArgumentRefinement(D_1) | +| g_class_init.py:42 | V2_0 = Str | +| g_class_init.py:43 | V3_0 = Str | +| g_class_init.py:45 | E_0 = ClassExpr | +| g_class_init.py:46 | V2_1 = ScopeEntryDefinition | +| g_class_init.py:46 | V3_1 = ScopeEntryDefinition | +| g_class_init.py:46 | __init___0 = FunctionExpr | +| g_class_init.py:46 | c_0 = ParameterDefinition | +| g_class_init.py:46 | c_3 = phi(c_1, c_2) | +| g_class_init.py:46 | self_0 = ParameterDefinition | +| g_class_init.py:46 | self_3 = phi(self_1, self_2) | +| g_class_init.py:48 | c_1 = Pi(c_0) [true] | +| g_class_init.py:48 | self_1 = AttributeAssignment 'version'(self_0) | +| g_class_init.py:50 | c_2 = Pi(c_0) [false] | +| g_class_init.py:50 | self_2 = AttributeAssignment 'version'(self_0) | +| g_class_init.py:52 | V2_2 = ScopeEntryDefinition | +| g_class_init.py:52 | meth_0 = FunctionExpr | +| g_class_init.py:52 | self_0 = ParameterDefinition | +| g_class_init.py:52 | self_2 = Pi(self_0) [false] | +| g_class_init.py:52 | self_3 = phi(self_1, self_2) | +| g_class_init.py:54 | self_1 = Pi(self_0) [true] | +| h_classes.py:0 | Base_0 = ScopeEntryDefinition | +| h_classes.py:0 | D_0 = ScopeEntryDefinition | +| h_classes.py:0 | Derived1_0 = ScopeEntryDefinition | +| h_classes.py:0 | Derived2_0 = ScopeEntryDefinition | +| h_classes.py:0 | Derived3_0 = ScopeEntryDefinition | +| h_classes.py:0 | __name___0 = ScopeEntryDefinition | +| h_classes.py:0 | __package___0 = ScopeEntryDefinition | +| h_classes.py:0 | f_0 = ScopeEntryDefinition | +| h_classes.py:0 | k_0 = ScopeEntryDefinition | +| h_classes.py:0 | thing_0 = ScopeEntryDefinition | +| h_classes.py:1 | sys_0 = ImportExpr | +| h_classes.py:3 | C_0 = ClassExpr | +| h_classes.py:5 | x_0 = Str | +| h_classes.py:7 | __init___0 = FunctionExpr | +| h_classes.py:7 | self_0 = ParameterDefinition | +| h_classes.py:8 | self_1 = AttributeAssignment 'y'(self_0) | +| h_classes.py:11 | sys_1 = ArgumentRefinement(sys_0) | +| h_classes.py:14 | C_1 = ScopeEntryDefinition | +| h_classes.py:14 | arg_0 = ParameterDefinition | +| h_classes.py:14 | k_1 = FunctionExpr | +| h_classes.py:14 | sys_2 = ScopeEntryDefinition | +| h_classes.py:17 | arg_1 = ArgumentRefinement(arg_0) | +| h_classes.py:23 | Base_1 = ClassExpr | +| h_classes.py:25 | Derived1_2 = ScopeEntryDefinition | +| h_classes.py:25 | Derived2_2 = ScopeEntryDefinition | +| h_classes.py:25 | Derived3_2 = ScopeEntryDefinition | +| h_classes.py:25 | __init___0 = FunctionExpr | +| h_classes.py:25 | choice_0 = ParameterDefinition | +| h_classes.py:25 | choice_5 = phi(choice_1, choice_3, choice_4) | +| h_classes.py:25 | self_0 = ParameterDefinition | +| h_classes.py:25 | self_4 = phi(self_1, self_2, self_3) | +| h_classes.py:27 | choice_1 = Pi(choice_0) [true] | +| h_classes.py:27 | self_1 = AttributeAssignment '__class__'(self_0) | +| h_classes.py:28 | choice_2 = Pi(choice_0) [false] | +| h_classes.py:29 | choice_3 = Pi(choice_2) [true] | +| h_classes.py:29 | self_2 = AttributeAssignment '__class__'(self_0) | +| h_classes.py:31 | choice_4 = Pi(choice_2) [false] | +| h_classes.py:31 | self_3 = AttributeAssignment '__class__'(self_0) | +| h_classes.py:33 | Derived1_1 = ClassExpr | +| h_classes.py:36 | Derived2_1 = ClassExpr | +| h_classes.py:39 | Derived3_1 = ClassExpr | +| h_classes.py:42 | thing_1 = Base() | +| h_classes.py:45 | arg0_0 = ParameterDefinition | +| h_classes.py:45 | arg1_0 = ParameterDefinition | +| h_classes.py:45 | arg2_0 = ParameterDefinition | +| h_classes.py:45 | f_1 = FunctionExpr | +| h_classes.py:48 | D_1 = ClassExpr | +| h_classes.py:48 | f_2 = ScopeEntryDefinition | +| h_classes.py:50 | m_0 = f | +| h_classes.py:52 | arg1_0 = ParameterDefinition | +| h_classes.py:52 | n_0 = FunctionExpr | +| h_classes.py:52 | self_0 = ParameterDefinition | +| i_imports.py:0 | *_0 = ScopeEntryDefinition | +| i_imports.py:0 | BytesIO_0 = ScopeEntryDefinition | +| i_imports.py:0 | StringIO_0 = ScopeEntryDefinition | +| i_imports.py:0 | __name___0 = ScopeEntryDefinition | +| i_imports.py:0 | __package___0 = ScopeEntryDefinition | +| i_imports.py:0 | _io_0 = ScopeEntryDefinition | +| i_imports.py:0 | argv_0 = ScopeEntryDefinition | +| i_imports.py:0 | code_0 = ScopeEntryDefinition | +| i_imports.py:0 | io_0 = ScopeEntryDefinition | +| i_imports.py:0 | sys_0 = ScopeEntryDefinition | +| i_imports.py:0 | xyz_0 = ScopeEntryDefinition | +| i_imports.py:0 | z_0 = ScopeEntryDefinition | +| i_imports.py:3 | a_0 = IntegerLiteral | +| i_imports.py:4 | b_0 = IntegerLiteral | +| i_imports.py:5 | c_0 = IntegerLiteral | +| i_imports.py:7 | *_1 = ImportStarRefinement(*_0) | +| i_imports.py:7 | BytesIO_1 = ImportStarRefinement(BytesIO_0) | +| i_imports.py:7 | StringIO_1 = ImportStarRefinement(StringIO_0) | +| i_imports.py:7 | __name___1 = ImportStarRefinement(__name___0) | +| i_imports.py:7 | __package___1 = ImportStarRefinement(__package___0) | +| i_imports.py:7 | _io_1 = ImportStarRefinement(_io_0) | +| i_imports.py:7 | a_1 = ImportStarRefinement(a_0) | +| i_imports.py:7 | b_1 = ImportStarRefinement(b_0) | +| i_imports.py:7 | c_1 = ImportStarRefinement(c_0) | +| i_imports.py:7 | io_1 = ImportStarRefinement(io_0) | +| i_imports.py:7 | z_1 = ImportStarRefinement(z_0) | +| i_imports.py:8 | xyz_1 = ImportMember | +| i_imports.py:13 | argv_1 = ImportMember | +| i_imports.py:17 | sys_1 = ImportExpr | +| i_imports.py:23 | code_1 = ImportExpr | +| i_imports.py:27 | *_2 = ImportStarRefinement(*_1) | +| i_imports.py:27 | __name___2 = ImportStarRefinement(__name___1) | +| i_imports.py:27 | __package___2 = ImportStarRefinement(__package___1) | +| i_imports.py:27 | a_2 = ImportStarRefinement(a_1) | +| i_imports.py:27 | argv_2 = ImportStarRefinement(argv_1) | +| i_imports.py:27 | b_2 = ImportStarRefinement(b_1) | +| i_imports.py:27 | c_2 = ImportStarRefinement(c_1) | +| i_imports.py:27 | sys_2 = ImportStarRefinement(sys_1) | +| i_imports.py:27 | xyz_2 = ImportStarRefinement(xyz_1) | +| i_imports.py:27 | z_2 = ImportStarRefinement(z_1) | +| i_imports.py:29 | _io_2 = ImportExpr | +| i_imports.py:33 | io_2 = ImportExpr | +| i_imports.py:34 | StringIO_2 = Attribute | +| i_imports.py:35 | BytesIO_2 = Attribute | +| i_imports.py:37 | code_2 = ImportExpr | +| j_convoluted_imports.py:0 | __name___0 = ScopeEntryDefinition | +| j_convoluted_imports.py:0 | __package___0 = ScopeEntryDefinition | +| j_convoluted_imports.py:3 | module_0 = ImportMember | +| j_convoluted_imports.py:6 | x_0 = ImportMember | +| j_convoluted_imports.py:9 | C_0 = ClassExpr | +| j_convoluted_imports.py:11 | module2_0 = ImportMember | +| j_convoluted_imports.py:13 | f_0 = FunctionExpr | +| j_convoluted_imports.py:13 | self_0 = ParameterDefinition | +| j_convoluted_imports.py:14 | x_0 = ImportMember | +| j_convoluted_imports.py:16 | moduleX_0 = ImportMember | +| k_getsetattr.py:0 | __name___0 = ScopeEntryDefinition | +| k_getsetattr.py:0 | __package___0 = ScopeEntryDefinition | +| k_getsetattr.py:4 | C_0 = ClassExpr | +| k_getsetattr.py:6 | meth1_0 = FunctionExpr | +| k_getsetattr.py:6 | self_0 = ParameterDefinition | +| k_getsetattr.py:7 | self_1 = ArgumentRefinement(self_0) | +| k_getsetattr.py:8 | self_2 = ArgumentRefinement(self_1) | +| k_getsetattr.py:9 | self_3 = ArgumentRefinement(self_2) | +| k_getsetattr.py:10 | self_4 = ArgumentRefinement(self_3) | +| k_getsetattr.py:12 | meth2_0 = FunctionExpr | +| k_getsetattr.py:12 | self_0 = ParameterDefinition | +| k_getsetattr.py:13 | self_1 = ArgumentRefinement(self_0) | +| k_getsetattr.py:14 | self_2 = ArgumentRefinement(self_1) | +| k_getsetattr.py:15 | self_3 = SelfCallsiteRefinement(self_2) | +| k_getsetattr.py:16 | self_4 = ArgumentRefinement(self_3) | +| k_getsetattr.py:17 | self_5 = ArgumentRefinement(self_4) | +| k_getsetattr.py:18 | self_6 = ArgumentRefinement(self_5) | +| k_getsetattr.py:21 | C_1 = ScopeEntryDefinition | +| k_getsetattr.py:21 | cond_0 = ParameterDefinition | +| k_getsetattr.py:21 | k_0 = FunctionExpr | +| k_getsetattr.py:22 | c1_0 = C() | +| k_getsetattr.py:23 | c2_0 = C() | +| k_getsetattr.py:24 | c3_0 = C() | +| k_getsetattr.py:25 | c1_1 = AttributeAssignment 'a'(c1_0) | +| k_getsetattr.py:27 | c2_1 = AttributeAssignment 'a'(c2_0) | +| k_getsetattr.py:27 | cond_1 = Pi(cond_0) [true] | +| k_getsetattr.py:28 | c2_2 = phi(c2_0, c2_1) | +| k_getsetattr.py:28 | cond_2 = Pi(cond_0) [false] | +| k_getsetattr.py:28 | cond_3 = phi(cond_1, cond_2) | +| k_getsetattr.py:31 | c3_1 = AttributeAssignment 'a'(c3_0) | +| n_nesting.py:0 | D_0 = ScopeEntryDefinition | +| n_nesting.py:0 | __name___0 = ScopeEntryDefinition | +| n_nesting.py:0 | __package___0 = ScopeEntryDefinition | +| n_nesting.py:8 | C_0 = ScopeEntryDefinition | +| n_nesting.py:8 | compile_ops_0 = ParameterDefinition | +| n_nesting.py:8 | foo_0 = FunctionExpr | +| n_nesting.py:9 | C_1 = CallsiteRefinement(C_0) | +| n_nesting.py:9 | compile_ops_1 = ArgumentRefinement(compile_ops_0) | +| n_nesting.py:10 | C_5 = ScopeEntryDefinition | +| n_nesting.py:10 | compile_ops_2 = Pi(compile_ops_1) [true] | +| n_nesting.py:10 | compile_ops_3 = ScopeEntryDefinition | +| n_nesting.py:10 | inner_0 = FunctionExpr | +| n_nesting.py:10 | node_def_0 = ParameterDefinition | +| n_nesting.py:11 | C_6 = CallsiteRefinement(C_5) | +| n_nesting.py:11 | node_def_1 = ArgumentRefinement(node_def_0) | +| n_nesting.py:13 | C_7 = ScopeEntryDefinition | +| n_nesting.py:13 | compile_ops_4 = Pi(compile_ops_1) [false] | +| n_nesting.py:13 | compile_ops_5 = ScopeEntryDefinition | +| n_nesting.py:13 | inner_1 = FunctionExpr | +| n_nesting.py:13 | node_def_0 = ParameterDefinition | +| n_nesting.py:14 | C_8 = CallsiteRefinement(C_7) | +| n_nesting.py:14 | node_def_1 = ArgumentRefinement(node_def_0) | +| n_nesting.py:15 | attrs_0 = Dict | +| n_nesting.py:16 | compile_ops_6 = phi(compile_ops_2, compile_ops_4) | +| n_nesting.py:16 | inner_2 = phi(inner_0, inner_1) | +| n_nesting.py:22 | C_9 = ScopeEntryDefinition | +| n_nesting.py:22 | f1_0 = FunctionExpr | +| n_nesting.py:23 | C_10 = AttributeAssignment 'flag'(C_9) | +| n_nesting.py:24 | C_11 = ScopeEntryDefinition | +| n_nesting.py:24 | f1_1 = ScopeEntryDefinition | +| n_nesting.py:24 | f2_0 = FunctionExpr | +| n_nesting.py:25 | C_12 = CallsiteRefinement(C_11) | +| n_nesting.py:26 | C_13 = ScopeEntryDefinition | +| n_nesting.py:26 | f2_1 = ScopeEntryDefinition | +| n_nesting.py:26 | f3_0 = FunctionExpr | +| n_nesting.py:27 | C_14 = CallsiteRefinement(C_13) | +| n_nesting.py:28 | C_15 = ScopeEntryDefinition | +| n_nesting.py:28 | f3_1 = ScopeEntryDefinition | +| n_nesting.py:28 | f4_0 = FunctionExpr | +| n_nesting.py:29 | C_16 = CallsiteRefinement(C_15) | +| n_nesting.py:30 | C_2 = ClassExpr | +| n_nesting.py:31 | C_3 = CallsiteRefinement(C_2) | +| n_nesting.py:32 | D_1 = ClassExpr | +| n_nesting.py:34 | C_4 = IntegerLiteral | +| r_regressions.py:0 | TestFirst_0 = ScopeEntryDefinition | +| r_regressions.py:0 | __name___0 = ScopeEntryDefinition | +| r_regressions.py:0 | __package___0 = ScopeEntryDefinition | +| r_regressions.py:0 | _names_0 = ScopeEntryDefinition | +| r_regressions.py:0 | _names_3 = Pi(_names_1) [false] | +| r_regressions.py:0 | _names_4 = phi(_names_2, _names_3) | +| r_regressions.py:0 | sys_0 = ScopeEntryDefinition | +| r_regressions.py:0 | t_0 = ScopeEntryDefinition | +| r_regressions.py:0 | t_2 = phi(t_0, t_1) | +| r_regressions.py:5 | Queue_0 = ClassExpr | +| r_regressions.py:7 | __init___0 = FunctionExpr | +| r_regressions.py:7 | self_0 = ParameterDefinition | +| r_regressions.py:9 | self_1 = SelfCallsiteRefinement(self_0) | +| r_regressions.py:11 | _after_fork_0 = FunctionExpr | +| r_regressions.py:11 | self_0 = ParameterDefinition | +| r_regressions.py:12 | self_1 = AttributeAssignment '_closed'(self_0) | +| r_regressions.py:13 | self_2 = AttributeAssignment '_close'(self_1) | +| r_regressions.py:15 | close_0 = FunctionExpr | +| r_regressions.py:15 | close_4 = Pi(close_0) [false] | +| r_regressions.py:15 | close_5 = phi(close_3, close_4) | +| r_regressions.py:15 | self_0 = ParameterDefinition | +| r_regressions.py:15 | self_3 = phi(self_1, self_2) | +| r_regressions.py:16 | self_1 = AttributeAssignment '_closed'(self_0) | +| r_regressions.py:20 | close_0 = Attribute | +| r_regressions.py:20 | close_1 = Attribute | +| r_regressions.py:21 | close_2 = SingleSuccessorGuard(close_1) [true] | +| r_regressions.py:22 | close_3 = Pi(close_0) [true] | +| r_regressions.py:22 | self_2 = AttributeAssignment '_close'(self_1) | +| r_regressions.py:27 | f_0 = FunctionExpr | +| r_regressions.py:27 | x_0 = ParameterDefinition | +| r_regressions.py:27 | x_5 = phi(x_3, x_4) | +| r_regressions.py:27 | y_0 = ParameterDefinition | +| r_regressions.py:27 | y_7 = Pi(y_2) [false] | +| r_regressions.py:27 | y_8 = phi(y_3, y_6, y_7) | +| r_regressions.py:27 | z_0 = ParameterDefinition | +| r_regressions.py:27 | z_3 = Pi(z_0) [false] | +| r_regressions.py:27 | z_4 = phi(z_0, z_2, z_3) | +| r_regressions.py:31 | x_1 = Pi(x_0) [true] | +| r_regressions.py:33 | x_2 = Pi(x_0) [false] | +| r_regressions.py:33 | x_3 = phi(x_1, x_2) | +| r_regressions.py:33 | y_1 = Pi(y_0) [false] | +| r_regressions.py:33 | y_2 = phi(y_0, y_1) | +| r_regressions.py:36 | y_3 = Pi(y_2) [true] | +| r_regressions.py:39 | x_4 = phi(x_1, x_3) | +| r_regressions.py:39 | y_4 = Pi(y_0) [true] | +| r_regressions.py:39 | y_5 = phi(y_3, y_4) | +| r_regressions.py:39 | y_6 = ArgumentRefinement(y_5) | +| r_regressions.py:39 | z_1 = Pi(z_0) [true] | +| r_regressions.py:39 | z_2 = phi(z_0, z_1) | +| r_regressions.py:42 | find_library_0 = FunctionExpr | +| r_regressions.py:42 | name_0 = ParameterDefinition | +| r_regressions.py:43 | __0 = ... | +| r_regressions.py:43 | data_0 = ... | +| r_regressions.py:46 | fail_0 = FunctionExpr | +| r_regressions.py:46 | msg_0 = ParameterDefinition | +| r_regressions.py:49 | C_0 = ClassExpr | +| r_regressions.py:51 | fail_0 = FunctionExpr | +| r_regressions.py:51 | fail_1 = ScopeEntryDefinition | +| r_regressions.py:51 | msg_0 = ParameterDefinition | +| r_regressions.py:51 | self_0 = ParameterDefinition | +| r_regressions.py:52 | msg_1 = ArgumentRefinement(msg_0) | +| r_regressions.py:58 | decorator_0 = ParameterDefinition | +| r_regressions.py:58 | method_decorator_0 = FunctionExpr | +| r_regressions.py:58 | name_0 = ParameterDefinition | +| r_regressions.py:61 | _dec_0 = FunctionExpr | +| r_regressions.py:61 | func_0 = ScopeEntryDefinition | +| r_regressions.py:61 | is_class_6 = phi(is_class_4, is_class_5) | +| r_regressions.py:61 | name_1 = ScopeEntryDefinition | +| r_regressions.py:61 | obj_0 = ParameterDefinition | +| r_regressions.py:61 | obj_3 = phi(obj_1, obj_2) | +| r_regressions.py:62 | is_class_0 = isinstance() | +| r_regressions.py:62 | name_2 = CallsiteRefinement(name_1) | +| r_regressions.py:62 | obj_1 = ArgumentRefinement(obj_0) | +| r_regressions.py:64 | is_class_1 = Pi(is_class_0) [true] | +| r_regressions.py:64 | name_3 = CallsiteRefinement(name_2) | +| r_regressions.py:66 | func_1 = obj | +| r_regressions.py:66 | is_class_2 = Pi(is_class_0) [false] | +| r_regressions.py:68 | _wrapper_0 = FunctionExpr | +| r_regressions.py:68 | args_0 = ParameterDefinition | +| r_regressions.py:68 | func_2 = phi(func_0, func_1) | +| r_regressions.py:68 | is_class_3 = phi(is_class_1, is_class_2) | +| r_regressions.py:68 | kwargs_0 = ParameterDefinition | +| r_regressions.py:68 | name_4 = phi(name_2, name_3) | +| r_regressions.py:68 | self_0 = ParameterDefinition | +| r_regressions.py:73 | is_class_4 = Pi(is_class_3) [true] | +| r_regressions.py:73 | obj_2 = ArgumentRefinement(obj_1) | +| r_regressions.py:76 | is_class_5 = Pi(is_class_3) [false] | +| r_regressions.py:80 | deco_0 = FunctionExpr | +| r_regressions.py:80 | func_0 = ParameterDefinition | +| r_regressions.py:81 | _wrapper_0 = FunctionExpr | +| r_regressions.py:81 | args_0 = ParameterDefinition | +| r_regressions.py:81 | kwargs_0 = ParameterDefinition | +| r_regressions.py:85 | deco_1 = ArgumentRefinement(deco_0) | +| r_regressions.py:86 | TestFirst_1 = method_decorator()() | +| r_regressions.py:87 | method_0 = FunctionExpr | +| r_regressions.py:87 | self_0 = ParameterDefinition | +| r_regressions.py:93 | sys_1 = ImportExpr | +| r_regressions.py:95 | _names_1 = Attribute | +| r_regressions.py:98 | _names_2 = Pi(_names_1) [true] | +| r_regressions.py:98 | t_1 = ImportExpr | +| s_scopes.py:0 | __name___0 = ScopeEntryDefinition | +| s_scopes.py:0 | __package___0 = ScopeEntryDefinition | +| s_scopes.py:0 | float_0 = ScopeEntryDefinition | +| s_scopes.py:0 | x_0 = ScopeEntryDefinition | +| s_scopes.py:4 | float_1 = True | +| s_scopes.py:5 | float_2 = phi(float_0, float_1) | +| s_scopes.py:7 | C2_0 = ClassExpr | +| s_scopes.py:7 | float_0 = ScopeEntryDefinition | +| s_scopes.py:7 | float_3 = ScopeEntryDefinition | +| s_scopes.py:7 | int_0 = ScopeEntryDefinition | +| s_scopes.py:7 | str_0 = ScopeEntryDefinition | +| s_scopes.py:9 | i1_0 = int | +| s_scopes.py:10 | f1_0 = float | +| s_scopes.py:12 | int_1 = IntegerLiteral | +| s_scopes.py:15 | str_1 = FloatLiteral | +| s_scopes.py:17 | float_1 = None | +| s_scopes.py:18 | float_2 = phi(float_0, float_1) | +| s_scopes.py:18 | i2_0 = int | +| s_scopes.py:18 | str_2 = phi(str_0, str_1) | +| s_scopes.py:19 | s_0 = str | +| s_scopes.py:20 | f2_0 = float | +| s_scopes.py:22 | x_1 = x | +| s_scopes.py:23 | i_0 = int | +| s_scopes.py:24 | f_0 = float | diff --git a/python/ql/test/library-tests/PointsTo/new/Dataflow.ql b/python/ql/test/library-tests/PointsTo/new/Dataflow.ql new file mode 100755 index 000000000000..1761c3bc4aba --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Dataflow.ql @@ -0,0 +1,8 @@ + + +import python +import Util + +from EssaVariable v, EssaDefinition def +where def = v.getDefinition() +select locate(def.getLocation(), "abdefghijknrs_"), v.getRepresentation() + " = " + def.getRepresentation() diff --git a/python/ql/test/library-tests/PointsTo/new/Definitions.expected b/python/ql/test/library-tests/PointsTo/new/Definitions.expected new file mode 100644 index 000000000000..f6576b569fc7 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Definitions.expected @@ -0,0 +1,428 @@ +| a_simple.py:0 | Global Variable __name__ | ScopeEntryDefinition | +| a_simple.py:0 | Global Variable __package__ | ScopeEntryDefinition | +| a_simple.py:2 | Global Variable f1 | AssignmentDefinition | +| a_simple.py:5 | Global Variable i1 | AssignmentDefinition | +| a_simple.py:6 | Global Variable s | AssignmentDefinition | +| a_simple.py:8 | Global Variable func | AssignmentDefinition | +| a_simple.py:11 | Global Variable C | AssignmentDefinition | +| a_simple.py:14 | Global Variable vararg_kwarg | AssignmentDefinition | +| a_simple.py:14 | Local Variable d | ParameterDefinition | +| a_simple.py:14 | Local Variable t | ParameterDefinition | +| a_simple.py:18 | Global Variable multi_loop | AssignmentDefinition | +| a_simple.py:18 | Local Variable seq | ParameterDefinition | +| a_simple.py:18 | Local Variable y | ScopeEntryDefinition | +| a_simple.py:19 | Local Variable x | AssignmentDefinition | +| a_simple.py:20 | Local Variable x | MultiAssignmentDefinition | +| a_simple.py:20 | Local Variable x | PhiFunction | +| a_simple.py:20 | Local Variable y | MultiAssignmentDefinition | +| a_simple.py:20 | Local Variable y | PhiFunction | +| a_simple.py:23 | Global Variable with_definition | AssignmentDefinition | +| a_simple.py:23 | Local Variable x | ParameterDefinition | +| a_simple.py:24 | Local Variable y | WithDefinition | +| a_simple.py:27 | Global Variable multi_loop_in_try | AssignmentDefinition | +| a_simple.py:27 | Local Variable p | ScopeEntryDefinition | +| a_simple.py:27 | Local Variable q | ScopeEntryDefinition | +| a_simple.py:27 | Local Variable x | ParameterDefinition | +| a_simple.py:29 | Local Variable p | MultiAssignmentDefinition | +| a_simple.py:29 | Local Variable p | PhiFunction | +| a_simple.py:29 | Local Variable q | MultiAssignmentDefinition | +| a_simple.py:29 | Local Variable q | PhiFunction | +| a_simple.py:34 | Global Variable f | AssignmentDefinition | +| a_simple.py:34 | Local Variable args | ParameterDefinition | +| a_simple.py:34 | Local Variable kwargs | ParameterDefinition | +| b_condition.py:0 | Global Variable __name__ | ScopeEntryDefinition | +| b_condition.py:0 | Global Variable __package__ | ScopeEntryDefinition | +| b_condition.py:0 | Global Variable double_attr_check | ScopeEntryDefinition | +| b_condition.py:0 | Global Variable g | ScopeEntryDefinition | +| b_condition.py:0 | Global Variable h | ScopeEntryDefinition | +| b_condition.py:0 | Global Variable k | ScopeEntryDefinition | +| b_condition.py:0 | Global Variable loop | ScopeEntryDefinition | +| b_condition.py:0 | Global Variable not_or_not | ScopeEntryDefinition | +| b_condition.py:0 | Global Variable odasa6261 | ScopeEntryDefinition | +| b_condition.py:0 | Global Variable split_bool1 | ScopeEntryDefinition | +| b_condition.py:0 | Global Variable v2 | ScopeEntryDefinition | +| b_condition.py:4 | Global Variable f | AssignmentDefinition | +| b_condition.py:4 | Local Variable y | ParameterDefinition | +| b_condition.py:5 | Local Variable x | AssignmentDefinition | +| b_condition.py:8 | Local Variable x | AssignmentDefinition | +| b_condition.py:9 | Local Variable x | PhiFunction | +| b_condition.py:9 | Local Variable x | PyEdgeRefinement | +| b_condition.py:11 | Local Variable x | AssignmentDefinition | +| b_condition.py:14 | Local Variable x | AssignmentDefinition | +| b_condition.py:15 | Local Variable x | PhiFunction | +| b_condition.py:15 | Local Variable x | PyEdgeRefinement | +| b_condition.py:17 | Local Variable x | AssignmentDefinition | +| b_condition.py:20 | Local Variable x | AssignmentDefinition | +| b_condition.py:21 | Local Variable x | PhiFunction | +| b_condition.py:21 | Local Variable x | PyEdgeRefinement | +| b_condition.py:23 | Local Variable x | AssignmentDefinition | +| b_condition.py:25 | Local Variable x | AssignmentDefinition | +| b_condition.py:25 | Local Variable x | PyEdgeRefinement | +| b_condition.py:26 | Local Variable x | ArgumentRefinement | +| b_condition.py:28 | Local Variable x | AssignmentDefinition | +| b_condition.py:29 | Local Variable x | PhiFunction | +| b_condition.py:31 | Local Variable x | AssignmentDefinition | +| b_condition.py:33 | Local Variable x | AssignmentDefinition | +| b_condition.py:34 | Local Variable x | ArgumentRefinement | +| b_condition.py:34 | Local Variable x | PhiFunction | +| b_condition.py:34 | Local Variable x | PyEdgeRefinement | +| b_condition.py:36 | Local Variable x | ArgumentRefinement | +| b_condition.py:36 | Local Variable x | PyEdgeRefinement | +| b_condition.py:37 | Local Variable x | ArgumentRefinement | +| b_condition.py:39 | Global Variable v2 | AssignmentDefinition | +| b_condition.py:41 | Global Variable v2 | AttributeAssignment | +| b_condition.py:43 | Global Variable v2 | PyEdgeRefinement | +| b_condition.py:47 | Global Variable v2 | PhiFunction | +| b_condition.py:47 | Global Variable v2 | PyEdgeRefinement | +| b_condition.py:50 | Global Variable g | AssignmentDefinition | +| b_condition.py:50 | Local Variable x | ParameterDefinition | +| b_condition.py:50 | Local Variable x | PhiFunction | +| b_condition.py:50 | Local Variable x | PyEdgeRefinement | +| b_condition.py:52 | Local Variable x | PyEdgeRefinement | +| b_condition.py:55 | Global Variable loop | AssignmentDefinition | +| b_condition.py:55 | Local Variable seq | ParameterDefinition | +| b_condition.py:55 | Local Variable v | ScopeEntryDefinition | +| b_condition.py:56 | Local Variable v | IterationDefinition | +| b_condition.py:56 | Local Variable v | PhiFunction | +| b_condition.py:56 | Local Variable v | PyEdgeRefinement | +| b_condition.py:58 | Local Variable v | ArgumentRefinement | +| b_condition.py:58 | Local Variable v | PyEdgeRefinement | +| b_condition.py:61 | Global Variable double_attr_check | AssignmentDefinition | +| b_condition.py:61 | Local Variable x | ParameterDefinition | +| b_condition.py:61 | Local Variable x | PhiFunction | +| b_condition.py:61 | Local Variable x | PyEdgeRefinement | +| b_condition.py:61 | Local Variable y | ParameterDefinition | +| b_condition.py:61 | Local Variable y | PhiFunction | +| b_condition.py:61 | Local Variable y | PyEdgeRefinement | +| b_condition.py:63 | Local Variable x | PyEdgeRefinement | +| b_condition.py:64 | Local Variable x | PyEdgeRefinement | +| b_condition.py:65 | Local Variable y | PyEdgeRefinement | +| b_condition.py:66 | Local Variable x | PyEdgeRefinement | +| b_condition.py:67 | Local Variable x | PyEdgeRefinement | +| b_condition.py:69 | Global Variable h | AssignmentDefinition | +| b_condition.py:70 | Local Variable b | AssignmentDefinition | +| b_condition.py:72 | Local Variable b | AssignmentDefinition | +| b_condition.py:73 | Local Variable b | PhiFunction | +| b_condition.py:73 | Local Variable b | PyEdgeRefinement | +| b_condition.py:75 | Global Variable k | AssignmentDefinition | +| b_condition.py:76 | Local Variable t | AssignmentDefinition | +| b_condition.py:78 | Local Variable t | AssignmentDefinition | +| b_condition.py:79 | Local Variable t | ArgumentRefinement | +| b_condition.py:79 | Local Variable t | PhiFunction | +| b_condition.py:79 | Local Variable t | PyEdgeRefinement | +| b_condition.py:81 | Global Variable odasa6261 | AssignmentDefinition | +| b_condition.py:81 | Local Variable bar | PhiFunction | +| b_condition.py:81 | Local Variable bar | ScopeEntryDefinition | +| b_condition.py:81 | Local Variable foo | ParameterDefinition | +| b_condition.py:81 | Local Variable foo | PhiFunction | +| b_condition.py:81 | Local Variable foo | PyEdgeRefinement | +| b_condition.py:82 | Local Variable foo | ArgumentRefinement | +| b_condition.py:83 | Local Variable bar | AssignmentDefinition | +| b_condition.py:83 | Local Variable foo | PyEdgeRefinement | +| b_condition.py:83 | Local Variable foo | ScopeEntryDefinition | +| b_condition.py:87 | Global Variable split_bool1 | AssignmentDefinition | +| b_condition.py:87 | Local Variable x | ParameterDefinition | +| b_condition.py:87 | Local Variable y | ParameterDefinition | +| b_condition.py:88 | Local Variable x | PyEdgeRefinement | +| b_condition.py:90 | Local Variable x | PyEdgeRefinement | +| b_condition.py:90 | Local Variable x | SingleSuccessorGuard | +| b_condition.py:90 | Local Variable y | PyEdgeRefinement | +| b_condition.py:92 | Local Variable x | SingleSuccessorGuard | +| b_condition.py:93 | Local Variable y | ArgumentRefinement | +| b_condition.py:95 | Local Variable y | ArgumentRefinement | +| b_condition.py:96 | Local Variable y | SingleSuccessorGuard | +| b_condition.py:97 | Local Variable x | ArgumentRefinement | +| b_condition.py:99 | Local Variable x | ArgumentRefinement | +| b_condition.py:101 | Global Variable not_or_not | AssignmentDefinition | +| b_condition.py:101 | Local Variable a | ParameterDefinition | +| b_condition.py:102 | Local Variable a | ArgumentRefinement | +| b_condition.py:104 | Local Variable a | PyEdgeRefinement | +| b_condition.py:105 | Local Variable a | PyEdgeRefinement | +| b_condition.py:107 | Local Variable a | PyEdgeRefinement | +| d_globals.py:0 | Global Variable D | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable Ugly | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable X | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable __name__ | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable __package__ | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable dict | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable get_g4 | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable k | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable modinit | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable outer | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable redefine | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable set_g4 | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable set_g4_indirect | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable tuple | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable use_list_attribute | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable x | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable y | ScopeEntryDefinition | +| d_globals.py:0 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:2 | Global Variable dict | ScopeEntryDefinition | +| d_globals.py:2 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:2 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:2 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:2 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:2 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:2 | Global Variable j | AssignmentDefinition | +| d_globals.py:2 | Global Variable tuple | ScopeEntryDefinition | +| d_globals.py:2 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:5 | Global Variable dict | AssignmentDefinition | +| d_globals.py:7 | Global Variable tuple | AssignmentDefinition | +| d_globals.py:14 | Global Variable g1 | AssignmentDefinition | +| d_globals.py:16 | Global Variable assign_global | AssignmentDefinition | +| d_globals.py:16 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:16 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:16 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:16 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:16 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:18 | Global Variable g1 | AssignmentDefinition | +| d_globals.py:23 | Global Variable g2 | AssignmentDefinition | +| d_globals.py:25 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:25 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:25 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:25 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:25 | Global Variable init | AssignmentDefinition | +| d_globals.py:25 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:27 | Global Variable g2 | AssignmentDefinition | +| d_globals.py:29 | Global Variable g1 | CallsiteRefinement | +| d_globals.py:29 | Global Variable g2 | CallsiteRefinement | +| d_globals.py:29 | Global Variable glob | CallsiteRefinement | +| d_globals.py:29 | Global Variable z | CallsiteRefinement | +| d_globals.py:33 | Global Variable g3 | AssignmentDefinition | +| d_globals.py:35 | Global Variable Ugly | AssignmentDefinition | +| d_globals.py:37 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:37 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:37 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:37 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:37 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:37 | Local Variable __init__ | AssignmentDefinition | +| d_globals.py:37 | Local Variable self | ParameterDefinition | +| d_globals.py:39 | Global Variable g3 | AssignmentDefinition | +| d_globals.py:41 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:41 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:41 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:41 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:41 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:41 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:41 | Local Variable meth | AssignmentDefinition | +| d_globals.py:41 | Local Variable self | ParameterDefinition | +| d_globals.py:46 | Global Variable x | AssignmentDefinition | +| d_globals.py:49 | Global Variable x | AssignmentDefinition | +| d_globals.py:51 | Global Variable x | PhiFunction | +| d_globals.py:52 | Global Variable y | AssignmentDefinition | +| d_globals.py:54 | Global Variable y | AssignmentDefinition | +| d_globals.py:59 | Global Variable y | PhiFunction | +| d_globals.py:62 | Global Variable X | AssignmentDefinition | +| d_globals.py:62 | Global Variable X | ScopeEntryDefinition | +| d_globals.py:62 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:62 | Global Variable y | ScopeEntryDefinition | +| d_globals.py:62 | Local Variable y | ScopeEntryDefinition | +| d_globals.py:63 | Local Variable y | AssignmentDefinition | +| d_globals.py:64 | Local Variable v4 | AssignmentDefinition | +| d_globals.py:70 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:70 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:70 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:70 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:70 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:70 | Global Variable k | AssignmentDefinition | +| d_globals.py:70 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:70 | Local Variable arg | ParameterDefinition | +| d_globals.py:73 | Global Variable g4 | AssignmentDefinition | +| d_globals.py:75 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:75 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:75 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:75 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:75 | Global Variable get_g4 | AssignmentDefinition | +| d_globals.py:75 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:75 | Global Variable set_g4 | ScopeEntryDefinition | +| d_globals.py:75 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:77 | Global Variable g1 | CallsiteRefinement | +| d_globals.py:77 | Global Variable g2 | CallsiteRefinement | +| d_globals.py:77 | Global Variable g3 | CallsiteRefinement | +| d_globals.py:77 | Global Variable g4 | CallsiteRefinement | +| d_globals.py:77 | Global Variable g4 | PyEdgeRefinement | +| d_globals.py:77 | Global Variable glob | CallsiteRefinement | +| d_globals.py:77 | Global Variable z | CallsiteRefinement | +| d_globals.py:78 | Global Variable g1 | PhiFunction | +| d_globals.py:78 | Global Variable g2 | PhiFunction | +| d_globals.py:78 | Global Variable g3 | PhiFunction | +| d_globals.py:78 | Global Variable g4 | PhiFunction | +| d_globals.py:78 | Global Variable g4 | PyEdgeRefinement | +| d_globals.py:78 | Global Variable glob | PhiFunction | +| d_globals.py:78 | Global Variable z | PhiFunction | +| d_globals.py:80 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:80 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:80 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:80 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:80 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:80 | Global Variable set_g4 | AssignmentDefinition | +| d_globals.py:80 | Global Variable set_g4_indirect | ScopeEntryDefinition | +| d_globals.py:80 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:81 | Global Variable g1 | CallsiteRefinement | +| d_globals.py:81 | Global Variable g2 | CallsiteRefinement | +| d_globals.py:81 | Global Variable g3 | CallsiteRefinement | +| d_globals.py:81 | Global Variable g4 | CallsiteRefinement | +| d_globals.py:81 | Global Variable glob | CallsiteRefinement | +| d_globals.py:81 | Global Variable z | CallsiteRefinement | +| d_globals.py:83 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:83 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:83 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:83 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:83 | Global Variable set_g4_indirect | AssignmentDefinition | +| d_globals.py:83 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:85 | Global Variable g4 | AssignmentDefinition | +| d_globals.py:87 | Global Variable modinit | AssignmentDefinition | +| d_globals.py:92 | Global Variable modinit | DeletionDefinition | +| d_globals.py:95 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:95 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:95 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:95 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:95 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:95 | Global Variable outer | AssignmentDefinition | +| d_globals.py:95 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:96 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:96 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:96 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:96 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:96 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:96 | Local Variable inner | AssignmentDefinition | +| d_globals.py:98 | Global Variable glob | AssignmentDefinition | +| d_globals.py:101 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:101 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:101 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:101 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:101 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:101 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:101 | Local Variable otherInner | AssignmentDefinition | +| d_globals.py:104 | Global Variable g1 | CallsiteRefinement | +| d_globals.py:104 | Global Variable g2 | CallsiteRefinement | +| d_globals.py:104 | Global Variable g3 | CallsiteRefinement | +| d_globals.py:104 | Global Variable g4 | CallsiteRefinement | +| d_globals.py:104 | Global Variable glob | CallsiteRefinement | +| d_globals.py:104 | Global Variable z | CallsiteRefinement | +| d_globals.py:107 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:107 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:107 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:107 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:107 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:107 | Global Variable redefine | AssignmentDefinition | +| d_globals.py:107 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:110 | Global Variable z | AssignmentDefinition | +| d_globals.py:113 | Global Variable glob | AssignmentDefinition | +| d_globals.py:118 | Global Variable D | AssignmentDefinition | +| d_globals.py:120 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:120 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:120 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:120 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:120 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:120 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:120 | Local Variable __init__ | AssignmentDefinition | +| d_globals.py:120 | Local Variable self | ParameterDefinition | +| d_globals.py:123 | Global Variable dict | ScopeEntryDefinition | +| d_globals.py:123 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:123 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:123 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:123 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:123 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:123 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:123 | Local Variable foo | AssignmentDefinition | +| d_globals.py:123 | Local Variable self | ParameterDefinition | +| d_globals.py:126 | Global Variable g1 | ScopeEntryDefinition | +| d_globals.py:126 | Global Variable g2 | ScopeEntryDefinition | +| d_globals.py:126 | Global Variable g3 | ScopeEntryDefinition | +| d_globals.py:126 | Global Variable g4 | ScopeEntryDefinition | +| d_globals.py:126 | Global Variable glob | ScopeEntryDefinition | +| d_globals.py:126 | Global Variable use_list_attribute | AssignmentDefinition | +| d_globals.py:126 | Global Variable z | ScopeEntryDefinition | +| d_globals.py:127 | Local Variable l | AssignmentDefinition | +| d_globals.py:128 | Global Variable g1 | CallsiteRefinement | +| d_globals.py:128 | Global Variable g2 | CallsiteRefinement | +| d_globals.py:128 | Global Variable g3 | CallsiteRefinement | +| d_globals.py:128 | Global Variable g4 | CallsiteRefinement | +| d_globals.py:128 | Global Variable glob | CallsiteRefinement | +| d_globals.py:128 | Global Variable z | CallsiteRefinement | +| d_globals.py:128 | Local Variable l | ArgumentRefinement | +| g_class_init.py:0 | Global Variable __name__ | ScopeEntryDefinition | +| g_class_init.py:0 | Global Variable __package__ | ScopeEntryDefinition | +| g_class_init.py:3 | Global Variable C | AssignmentDefinition | +| g_class_init.py:5 | Local Variable __init__ | AssignmentDefinition | +| g_class_init.py:5 | Local Variable self | ParameterDefinition | +| g_class_init.py:6 | Local Variable self | SelfCallsiteRefinement | +| g_class_init.py:7 | Local Variable self | AttributeAssignment | +| g_class_init.py:9 | Local Variable _init | AssignmentDefinition | +| g_class_init.py:9 | Local Variable self | ParameterDefinition | +| g_class_init.py:10 | Local Variable self | AttributeAssignment | +| g_class_init.py:11 | Local Variable self | SelfCallsiteRefinement | +| g_class_init.py:13 | Local Variable _init2 | AssignmentDefinition | +| g_class_init.py:13 | Local Variable self | ParameterDefinition | +| g_class_init.py:14 | Local Variable self | AttributeAssignment | +| g_class_init.py:16 | Local Variable method | AssignmentDefinition | +| g_class_init.py:16 | Local Variable self | ParameterDefinition | +| g_class_init.py:19 | Local Variable self | PyEdgeRefinement | +| g_class_init.py:20 | Local Variable self | PhiFunction | +| g_class_init.py:20 | Local Variable self | PyEdgeRefinement | +| g_class_init.py:24 | Global Variable Oddities | AssignmentDefinition | +| g_class_init.py:24 | Local Variable float | ScopeEntryDefinition | +| g_class_init.py:24 | Local Variable int | ScopeEntryDefinition | +| g_class_init.py:26 | Local Variable int | AssignmentDefinition | +| g_class_init.py:27 | Local Variable float | AssignmentDefinition | +| g_class_init.py:28 | Local Variable l | AssignmentDefinition | +| g_class_init.py:29 | Local Variable h | AssignmentDefinition | +| g_class_init.py:32 | Global Variable D | AssignmentDefinition | +| g_class_init.py:34 | Global Variable D | ScopeEntryDefinition | +| g_class_init.py:34 | Local Variable __init__ | AssignmentDefinition | +| g_class_init.py:34 | Local Variable self | ParameterDefinition | +| g_class_init.py:35 | Global Variable D | ArgumentRefinement | +| g_class_init.py:42 | Global Variable V2 | AssignmentDefinition | +| g_class_init.py:43 | Global Variable V3 | AssignmentDefinition | +| g_class_init.py:45 | Global Variable E | AssignmentDefinition | +| g_class_init.py:46 | Global Variable V2 | ScopeEntryDefinition | +| g_class_init.py:46 | Global Variable V3 | ScopeEntryDefinition | +| g_class_init.py:46 | Local Variable __init__ | AssignmentDefinition | +| g_class_init.py:46 | Local Variable c | ParameterDefinition | +| g_class_init.py:46 | Local Variable c | PhiFunction | +| g_class_init.py:46 | Local Variable self | ParameterDefinition | +| g_class_init.py:46 | Local Variable self | PhiFunction | +| g_class_init.py:48 | Local Variable c | PyEdgeRefinement | +| g_class_init.py:48 | Local Variable self | AttributeAssignment | +| g_class_init.py:50 | Local Variable c | PyEdgeRefinement | +| g_class_init.py:50 | Local Variable self | AttributeAssignment | +| g_class_init.py:52 | Global Variable V2 | ScopeEntryDefinition | +| g_class_init.py:52 | Local Variable meth | AssignmentDefinition | +| g_class_init.py:52 | Local Variable self | ParameterDefinition | +| g_class_init.py:52 | Local Variable self | PhiFunction | +| g_class_init.py:52 | Local Variable self | PyEdgeRefinement | +| g_class_init.py:54 | Local Variable self | PyEdgeRefinement | +| k_getsetattr.py:0 | Global Variable __name__ | ScopeEntryDefinition | +| k_getsetattr.py:0 | Global Variable __package__ | ScopeEntryDefinition | +| k_getsetattr.py:4 | Global Variable C | AssignmentDefinition | +| k_getsetattr.py:6 | Local Variable meth1 | AssignmentDefinition | +| k_getsetattr.py:6 | Local Variable self | ParameterDefinition | +| k_getsetattr.py:7 | Local Variable self | ArgumentRefinement | +| k_getsetattr.py:8 | Local Variable self | ArgumentRefinement | +| k_getsetattr.py:9 | Local Variable self | ArgumentRefinement | +| k_getsetattr.py:10 | Local Variable self | ArgumentRefinement | +| k_getsetattr.py:12 | Local Variable meth2 | AssignmentDefinition | +| k_getsetattr.py:12 | Local Variable self | ParameterDefinition | +| k_getsetattr.py:13 | Local Variable self | ArgumentRefinement | +| k_getsetattr.py:14 | Local Variable self | ArgumentRefinement | +| k_getsetattr.py:15 | Local Variable self | SelfCallsiteRefinement | +| k_getsetattr.py:16 | Local Variable self | ArgumentRefinement | +| k_getsetattr.py:17 | Local Variable self | ArgumentRefinement | +| k_getsetattr.py:18 | Local Variable self | ArgumentRefinement | +| k_getsetattr.py:21 | Global Variable C | ScopeEntryDefinition | +| k_getsetattr.py:21 | Global Variable k | AssignmentDefinition | +| k_getsetattr.py:21 | Local Variable cond | ParameterDefinition | +| k_getsetattr.py:22 | Local Variable c1 | AssignmentDefinition | +| k_getsetattr.py:23 | Local Variable c2 | AssignmentDefinition | +| k_getsetattr.py:24 | Local Variable c3 | AssignmentDefinition | +| k_getsetattr.py:25 | Local Variable c1 | AttributeAssignment | +| k_getsetattr.py:27 | Local Variable c2 | AttributeAssignment | +| k_getsetattr.py:27 | Local Variable cond | PyEdgeRefinement | +| k_getsetattr.py:28 | Local Variable c2 | PhiFunction | +| k_getsetattr.py:28 | Local Variable cond | PhiFunction | +| k_getsetattr.py:28 | Local Variable cond | PyEdgeRefinement | +| k_getsetattr.py:31 | Local Variable c3 | AttributeAssignment | diff --git a/python/ql/test/library-tests/PointsTo/new/Definitions.ql b/python/ql/test/library-tests/PointsTo/new/Definitions.ql new file mode 100644 index 000000000000..166e93918689 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Definitions.ql @@ -0,0 +1,8 @@ + +import python + +import Util + +from EssaDefinition def, Variable v +where v = def.getSourceVariable() +select locate(def.getLocation(), "abdgk"), v.toString(), def.getAQlClass() \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/new/Live.expected b/python/ql/test/library-tests/PointsTo/new/Live.expected new file mode 100644 index 000000000000..a0a70d136c79 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Live.expected @@ -0,0 +1,424 @@ +| Global Variable Exception | b_condition.py:0 | entry | +| Global Variable Exception | b_condition.py:42 | exit | +| Global Variable Exception | b_condition.py:43 | entry | +| Global Variable Exception | b_condition.py:44 | exit | +| Global Variable Exception | b_condition.py:47 | entry | +| Global Variable Exception | b_condition.py:101 | entry | +| Global Variable Exception | b_condition.py:102 | exit | +| Global Variable Exception | b_condition.py:104 | entry | +| Global Variable Exception | b_condition.py:104 | exit | +| Global Variable Exception | b_condition.py:105 | entry | +| Global Variable Exception | b_condition.py:105 | exit | +| Global Variable Exception | b_condition.py:106 | entry | +| Global Variable TypeError | b_condition.py:0 | entry | +| Global Variable TypeError | b_condition.py:42 | exit | +| Global Variable TypeError | b_condition.py:43 | entry | +| Global Variable TypeError | b_condition.py:44 | exit | +| Global Variable TypeError | b_condition.py:47 | entry | +| Global Variable TypeError | b_condition.py:101 | entry | +| Global Variable TypeError | b_condition.py:102 | exit | +| Global Variable TypeError | b_condition.py:103 | entry | +| Global Variable __name__ | b_condition.py:42 | exit | +| Global Variable __name__ | b_condition.py:43 | entry | +| Global Variable __name__ | b_condition.py:44 | exit | +| Global Variable __name__ | b_condition.py:47 | entry | +| Global Variable __package__ | b_condition.py:42 | exit | +| Global Variable __package__ | b_condition.py:43 | entry | +| Global Variable __package__ | b_condition.py:44 | exit | +| Global Variable __package__ | b_condition.py:47 | entry | +| Global Variable callable | b_condition.py:0 | entry | +| Global Variable callable | b_condition.py:42 | exit | +| Global Variable callable | b_condition.py:43 | entry | +| Global Variable callable | b_condition.py:44 | exit | +| Global Variable callable | b_condition.py:47 | entry | +| Global Variable callable | b_condition.py:81 | entry | +| Global Variable cond | b_condition.py:0 | entry | +| Global Variable cond | b_condition.py:4 | entry | +| Global Variable cond | b_condition.py:5 | entry | +| Global Variable cond | b_condition.py:5 | exit | +| Global Variable cond | b_condition.py:7 | exit | +| Global Variable cond | b_condition.py:8 | entry | +| Global Variable cond | b_condition.py:8 | exit | +| Global Variable cond | b_condition.py:9 | entry | +| Global Variable cond | b_condition.py:11 | entry | +| Global Variable cond | b_condition.py:11 | exit | +| Global Variable cond | b_condition.py:13 | exit | +| Global Variable cond | b_condition.py:14 | entry | +| Global Variable cond | b_condition.py:14 | exit | +| Global Variable cond | b_condition.py:15 | entry | +| Global Variable cond | b_condition.py:17 | entry | +| Global Variable cond | b_condition.py:17 | exit | +| Global Variable cond | b_condition.py:19 | exit | +| Global Variable cond | b_condition.py:20 | entry | +| Global Variable cond | b_condition.py:20 | exit | +| Global Variable cond | b_condition.py:21 | entry | +| Global Variable cond | b_condition.py:23 | entry | +| Global Variable cond | b_condition.py:23 | exit | +| Global Variable cond | b_condition.py:25 | entry | +| Global Variable cond | b_condition.py:25 | exit | +| Global Variable cond | b_condition.py:27 | exit | +| Global Variable cond | b_condition.py:28 | entry | +| Global Variable cond | b_condition.py:28 | exit | +| Global Variable cond | b_condition.py:29 | entry | +| Global Variable cond | b_condition.py:42 | exit | +| Global Variable cond | b_condition.py:43 | entry | +| Global Variable cond | b_condition.py:44 | exit | +| Global Variable cond | b_condition.py:47 | entry | +| Global Variable cond | b_condition.py:69 | entry | +| Global Variable double_attr_check | b_condition.py:42 | exit | +| Global Variable double_attr_check | b_condition.py:43 | entry | +| Global Variable f | b_condition.py:42 | exit | +| Global Variable f | b_condition.py:43 | entry | +| Global Variable f | b_condition.py:44 | exit | +| Global Variable f | b_condition.py:47 | entry | +| Global Variable g | b_condition.py:42 | exit | +| Global Variable g | b_condition.py:43 | entry | +| Global Variable h | b_condition.py:42 | exit | +| Global Variable h | b_condition.py:43 | entry | +| Global Variable int | b_condition.py:0 | entry | +| Global Variable int | b_condition.py:4 | entry | +| Global Variable int | b_condition.py:5 | entry | +| Global Variable int | b_condition.py:5 | exit | +| Global Variable int | b_condition.py:7 | exit | +| Global Variable int | b_condition.py:8 | entry | +| Global Variable int | b_condition.py:8 | exit | +| Global Variable int | b_condition.py:9 | entry | +| Global Variable int | b_condition.py:11 | entry | +| Global Variable int | b_condition.py:11 | exit | +| Global Variable int | b_condition.py:13 | exit | +| Global Variable int | b_condition.py:14 | entry | +| Global Variable int | b_condition.py:14 | exit | +| Global Variable int | b_condition.py:15 | entry | +| Global Variable int | b_condition.py:17 | entry | +| Global Variable int | b_condition.py:17 | exit | +| Global Variable int | b_condition.py:19 | exit | +| Global Variable int | b_condition.py:20 | entry | +| Global Variable int | b_condition.py:20 | exit | +| Global Variable int | b_condition.py:21 | entry | +| Global Variable int | b_condition.py:23 | entry | +| Global Variable int | b_condition.py:23 | exit | +| Global Variable int | b_condition.py:25 | entry | +| Global Variable int | b_condition.py:25 | exit | +| Global Variable int | b_condition.py:27 | exit | +| Global Variable int | b_condition.py:28 | entry | +| Global Variable int | b_condition.py:28 | exit | +| Global Variable int | b_condition.py:29 | entry | +| Global Variable int | b_condition.py:31 | entry | +| Global Variable int | b_condition.py:31 | exit | +| Global Variable int | b_condition.py:32 | exit | +| Global Variable int | b_condition.py:33 | entry | +| Global Variable int | b_condition.py:33 | exit | +| Global Variable int | b_condition.py:34 | entry | +| Global Variable int | b_condition.py:42 | exit | +| Global Variable int | b_condition.py:43 | entry | +| Global Variable int | b_condition.py:44 | exit | +| Global Variable int | b_condition.py:47 | entry | +| Global Variable isinstance | b_condition.py:0 | entry | +| Global Variable isinstance | b_condition.py:4 | entry | +| Global Variable isinstance | b_condition.py:5 | entry | +| Global Variable isinstance | b_condition.py:5 | exit | +| Global Variable isinstance | b_condition.py:7 | exit | +| Global Variable isinstance | b_condition.py:8 | entry | +| Global Variable isinstance | b_condition.py:8 | exit | +| Global Variable isinstance | b_condition.py:9 | entry | +| Global Variable isinstance | b_condition.py:11 | entry | +| Global Variable isinstance | b_condition.py:11 | exit | +| Global Variable isinstance | b_condition.py:13 | exit | +| Global Variable isinstance | b_condition.py:14 | entry | +| Global Variable isinstance | b_condition.py:14 | exit | +| Global Variable isinstance | b_condition.py:15 | entry | +| Global Variable isinstance | b_condition.py:17 | entry | +| Global Variable isinstance | b_condition.py:17 | exit | +| Global Variable isinstance | b_condition.py:19 | exit | +| Global Variable isinstance | b_condition.py:20 | entry | +| Global Variable isinstance | b_condition.py:20 | exit | +| Global Variable isinstance | b_condition.py:21 | entry | +| Global Variable isinstance | b_condition.py:23 | entry | +| Global Variable isinstance | b_condition.py:23 | exit | +| Global Variable isinstance | b_condition.py:25 | entry | +| Global Variable isinstance | b_condition.py:25 | exit | +| Global Variable isinstance | b_condition.py:27 | exit | +| Global Variable isinstance | b_condition.py:28 | entry | +| Global Variable isinstance | b_condition.py:28 | exit | +| Global Variable isinstance | b_condition.py:29 | entry | +| Global Variable isinstance | b_condition.py:31 | entry | +| Global Variable isinstance | b_condition.py:31 | exit | +| Global Variable isinstance | b_condition.py:32 | exit | +| Global Variable isinstance | b_condition.py:33 | entry | +| Global Variable isinstance | b_condition.py:33 | exit | +| Global Variable isinstance | b_condition.py:34 | entry | +| Global Variable isinstance | b_condition.py:42 | exit | +| Global Variable isinstance | b_condition.py:43 | entry | +| Global Variable isinstance | b_condition.py:44 | exit | +| Global Variable isinstance | b_condition.py:47 | entry | +| Global Variable isinstance | b_condition.py:101 | entry | +| Global Variable k | b_condition.py:42 | exit | +| Global Variable k | b_condition.py:43 | entry | +| Global Variable list | b_condition.py:0 | entry | +| Global Variable list | b_condition.py:42 | exit | +| Global Variable list | b_condition.py:43 | entry | +| Global Variable list | b_condition.py:44 | exit | +| Global Variable list | b_condition.py:47 | entry | +| Global Variable list | b_condition.py:101 | entry | +| Global Variable loop | b_condition.py:42 | exit | +| Global Variable loop | b_condition.py:43 | entry | +| Global Variable not_or_not | b_condition.py:42 | exit | +| Global Variable not_or_not | b_condition.py:43 | entry | +| Global Variable object | b_condition.py:0 | entry | +| Global Variable object | b_condition.py:42 | exit | +| Global Variable object | b_condition.py:43 | entry | +| Global Variable object | b_condition.py:44 | exit | +| Global Variable object | b_condition.py:47 | entry | +| Global Variable object | b_condition.py:75 | entry | +| Global Variable object | b_condition.py:77 | exit | +| Global Variable object | b_condition.py:78 | entry | +| Global Variable odasa6261 | b_condition.py:42 | exit | +| Global Variable odasa6261 | b_condition.py:43 | entry | +| Global Variable seq | b_condition.py:0 | entry | +| Global Variable seq | b_condition.py:42 | exit | +| Global Variable seq | b_condition.py:43 | entry | +| Global Variable seq | b_condition.py:44 | exit | +| Global Variable seq | b_condition.py:47 | entry | +| Global Variable seq | b_condition.py:61 | entry | +| Global Variable seq | b_condition.py:62 | exit | +| Global Variable seq | b_condition.py:64 | entry | +| Global Variable seq | b_condition.py:64 | exit | +| Global Variable seq | b_condition.py:65 | entry | +| Global Variable seq | b_condition.py:65 | exit | +| Global Variable seq | b_condition.py:66 | entry | +| Global Variable split_bool1 | b_condition.py:42 | exit | +| Global Variable split_bool1 | b_condition.py:43 | entry | +| Global Variable thing | b_condition.py:0 | entry | +| Global Variable thing | b_condition.py:42 | exit | +| Global Variable thing | b_condition.py:43 | entry | +| Global Variable thing | b_condition.py:44 | exit | +| Global Variable thing | b_condition.py:47 | entry | +| Global Variable tuple | b_condition.py:0 | entry | +| Global Variable tuple | b_condition.py:42 | exit | +| Global Variable tuple | b_condition.py:43 | entry | +| Global Variable tuple | b_condition.py:44 | exit | +| Global Variable tuple | b_condition.py:47 | entry | +| Global Variable tuple | b_condition.py:101 | entry | +| Global Variable type | b_condition.py:0 | entry | +| Global Variable type | b_condition.py:42 | exit | +| Global Variable type | b_condition.py:43 | entry | +| Global Variable type | b_condition.py:44 | exit | +| Global Variable type | b_condition.py:47 | entry | +| Global Variable type | b_condition.py:75 | entry | +| Global Variable unknown | b_condition.py:0 | entry | +| Global Variable unknown | b_condition.py:4 | entry | +| Global Variable unknown | b_condition.py:5 | entry | +| Global Variable unknown | b_condition.py:5 | exit | +| Global Variable unknown | b_condition.py:7 | exit | +| Global Variable unknown | b_condition.py:8 | entry | +| Global Variable unknown | b_condition.py:8 | exit | +| Global Variable unknown | b_condition.py:9 | entry | +| Global Variable unknown | b_condition.py:11 | entry | +| Global Variable unknown | b_condition.py:11 | exit | +| Global Variable unknown | b_condition.py:13 | exit | +| Global Variable unknown | b_condition.py:14 | entry | +| Global Variable unknown | b_condition.py:14 | exit | +| Global Variable unknown | b_condition.py:15 | entry | +| Global Variable unknown | b_condition.py:17 | entry | +| Global Variable unknown | b_condition.py:17 | exit | +| Global Variable unknown | b_condition.py:19 | exit | +| Global Variable unknown | b_condition.py:20 | entry | +| Global Variable unknown | b_condition.py:20 | exit | +| Global Variable unknown | b_condition.py:21 | entry | +| Global Variable unknown | b_condition.py:23 | entry | +| Global Variable unknown | b_condition.py:23 | exit | +| Global Variable unknown | b_condition.py:25 | entry | +| Global Variable unknown | b_condition.py:25 | exit | +| Global Variable unknown | b_condition.py:27 | exit | +| Global Variable unknown | b_condition.py:28 | entry | +| Global Variable unknown | b_condition.py:28 | exit | +| Global Variable unknown | b_condition.py:29 | entry | +| Global Variable unknown | b_condition.py:31 | entry | +| Global Variable unknown | b_condition.py:31 | exit | +| Global Variable unknown | b_condition.py:42 | exit | +| Global Variable unknown | b_condition.py:43 | entry | +| Global Variable unknown | b_condition.py:44 | exit | +| Global Variable unknown | b_condition.py:47 | entry | +| Global Variable unknown | b_condition.py:69 | entry | +| Global Variable unknown | b_condition.py:70 | entry | +| Global Variable unknown | b_condition.py:70 | exit | +| Global Variable use | b_condition.py:0 | entry | +| Global Variable use | b_condition.py:4 | entry | +| Global Variable use | b_condition.py:5 | entry | +| Global Variable use | b_condition.py:5 | exit | +| Global Variable use | b_condition.py:7 | exit | +| Global Variable use | b_condition.py:8 | entry | +| Global Variable use | b_condition.py:8 | exit | +| Global Variable use | b_condition.py:9 | entry | +| Global Variable use | b_condition.py:11 | entry | +| Global Variable use | b_condition.py:11 | exit | +| Global Variable use | b_condition.py:13 | exit | +| Global Variable use | b_condition.py:14 | entry | +| Global Variable use | b_condition.py:14 | exit | +| Global Variable use | b_condition.py:15 | entry | +| Global Variable use | b_condition.py:17 | entry | +| Global Variable use | b_condition.py:17 | exit | +| Global Variable use | b_condition.py:19 | exit | +| Global Variable use | b_condition.py:20 | entry | +| Global Variable use | b_condition.py:20 | exit | +| Global Variable use | b_condition.py:21 | entry | +| Global Variable use | b_condition.py:23 | entry | +| Global Variable use | b_condition.py:23 | exit | +| Global Variable use | b_condition.py:25 | entry | +| Global Variable use | b_condition.py:25 | exit | +| Global Variable use | b_condition.py:27 | exit | +| Global Variable use | b_condition.py:28 | entry | +| Global Variable use | b_condition.py:28 | exit | +| Global Variable use | b_condition.py:29 | entry | +| Global Variable use | b_condition.py:31 | entry | +| Global Variable use | b_condition.py:31 | exit | +| Global Variable use | b_condition.py:32 | exit | +| Global Variable use | b_condition.py:33 | entry | +| Global Variable use | b_condition.py:33 | exit | +| Global Variable use | b_condition.py:34 | entry | +| Global Variable use | b_condition.py:36 | entry | +| Global Variable use | b_condition.py:36 | exit | +| Global Variable use | b_condition.py:42 | exit | +| Global Variable use | b_condition.py:43 | entry | +| Global Variable use | b_condition.py:44 | exit | +| Global Variable use | b_condition.py:47 | entry | +| Global Variable use | b_condition.py:55 | entry | +| Global Variable use | b_condition.py:56 | entry | +| Global Variable use | b_condition.py:56 | exit | +| Global Variable use | b_condition.py:57 | exit | +| Global Variable use | b_condition.py:58 | entry | +| Global Variable use | b_condition.py:58 | exit | +| Global Variable use | b_condition.py:75 | entry | +| Global Variable use | b_condition.py:77 | exit | +| Global Variable use | b_condition.py:78 | entry | +| Global Variable use | b_condition.py:78 | exit | +| Global Variable use | b_condition.py:79 | entry | +| Global Variable use | b_condition.py:87 | entry | +| Global Variable use | b_condition.py:88 | entry | +| Global Variable use | b_condition.py:88 | exit | +| Global Variable use | b_condition.py:90 | entry | +| Global Variable use | b_condition.py:90 | exit | +| Global Variable v2 | b_condition.py:42 | exit | +| Global Variable v2 | b_condition.py:43 | entry | +| Global Variable v2 | b_condition.py:44 | exit | +| Global Variable v2 | b_condition.py:47 | entry | +| Local Variable a | b_condition.py:102 | exit | +| Local Variable a | b_condition.py:104 | entry | +| Local Variable a | b_condition.py:104 | exit | +| Local Variable a | b_condition.py:105 | entry | +| Local Variable a | b_condition.py:105 | exit | +| Local Variable a | b_condition.py:107 | entry | +| Local Variable b | b_condition.py:71 | exit | +| Local Variable b | b_condition.py:72 | exit | +| Local Variable b | b_condition.py:73 | entry | +| Local Variable bar | b_condition.py:81 | entry | +| Local Variable bar | b_condition.py:82 | exit | +| Local Variable bar | b_condition.py:83 | exit | +| Local Variable foo | b_condition.py:81 | entry | +| Local Variable foo | b_condition.py:82 | exit | +| Local Variable foo | b_condition.py:83 | entry | +| Local Variable foo | b_condition.py:83 | exit | +| Local Variable seq | b_condition.py:55 | entry | +| Local Variable seq | b_condition.py:56 | entry | +| Local Variable seq | b_condition.py:56 | exit | +| Local Variable seq | b_condition.py:57 | exit | +| Local Variable seq | b_condition.py:58 | entry | +| Local Variable seq | b_condition.py:58 | exit | +| Local Variable t | b_condition.py:77 | exit | +| Local Variable t | b_condition.py:78 | exit | +| Local Variable t | b_condition.py:79 | entry | +| Local Variable v | b_condition.py:55 | entry | +| Local Variable v | b_condition.py:56 | entry | +| Local Variable v | b_condition.py:56 | exit | +| Local Variable v | b_condition.py:57 | exit | +| Local Variable v | b_condition.py:58 | entry | +| Local Variable v | b_condition.py:58 | exit | +| Local Variable x | b_condition.py:7 | exit | +| Local Variable x | b_condition.py:8 | exit | +| Local Variable x | b_condition.py:9 | entry | +| Local Variable x | b_condition.py:13 | exit | +| Local Variable x | b_condition.py:14 | exit | +| Local Variable x | b_condition.py:15 | entry | +| Local Variable x | b_condition.py:19 | exit | +| Local Variable x | b_condition.py:20 | exit | +| Local Variable x | b_condition.py:21 | entry | +| Local Variable x | b_condition.py:25 | entry | +| Local Variable x | b_condition.py:25 | exit | +| Local Variable x | b_condition.py:27 | exit | +| Local Variable x | b_condition.py:28 | exit | +| Local Variable x | b_condition.py:29 | entry | +| Local Variable x | b_condition.py:32 | exit | +| Local Variable x | b_condition.py:33 | exit | +| Local Variable x | b_condition.py:34 | entry | +| Local Variable x | b_condition.py:36 | entry | +| Local Variable x | b_condition.py:36 | exit | +| Local Variable x | b_condition.py:50 | entry | +| Local Variable x | b_condition.py:51 | exit | +| Local Variable x | b_condition.py:52 | entry | +| Local Variable x | b_condition.py:52 | exit | +| Local Variable x | b_condition.py:61 | entry | +| Local Variable x | b_condition.py:62 | exit | +| Local Variable x | b_condition.py:63 | entry | +| Local Variable x | b_condition.py:63 | exit | +| Local Variable x | b_condition.py:64 | entry | +| Local Variable x | b_condition.py:64 | exit | +| Local Variable x | b_condition.py:65 | entry | +| Local Variable x | b_condition.py:65 | exit | +| Local Variable x | b_condition.py:66 | entry | +| Local Variable x | b_condition.py:66 | exit | +| Local Variable x | b_condition.py:67 | entry | +| Local Variable x | b_condition.py:67 | exit | +| Local Variable x | b_condition.py:88 | entry | +| Local Variable x | b_condition.py:88 | exit | +| Local Variable x | b_condition.py:90 | entry | +| Local Variable x | b_condition.py:90 | exit | +| Local Variable y | b_condition.py:5 | entry | +| Local Variable y | b_condition.py:5 | exit | +| Local Variable y | b_condition.py:7 | exit | +| Local Variable y | b_condition.py:8 | entry | +| Local Variable y | b_condition.py:8 | exit | +| Local Variable y | b_condition.py:9 | entry | +| Local Variable y | b_condition.py:11 | entry | +| Local Variable y | b_condition.py:11 | exit | +| Local Variable y | b_condition.py:13 | exit | +| Local Variable y | b_condition.py:14 | entry | +| Local Variable y | b_condition.py:14 | exit | +| Local Variable y | b_condition.py:15 | entry | +| Local Variable y | b_condition.py:17 | entry | +| Local Variable y | b_condition.py:17 | exit | +| Local Variable y | b_condition.py:19 | exit | +| Local Variable y | b_condition.py:20 | entry | +| Local Variable y | b_condition.py:20 | exit | +| Local Variable y | b_condition.py:21 | entry | +| Local Variable y | b_condition.py:23 | entry | +| Local Variable y | b_condition.py:23 | exit | +| Local Variable y | b_condition.py:25 | entry | +| Local Variable y | b_condition.py:25 | exit | +| Local Variable y | b_condition.py:27 | exit | +| Local Variable y | b_condition.py:28 | entry | +| Local Variable y | b_condition.py:28 | exit | +| Local Variable y | b_condition.py:29 | entry | +| Local Variable y | b_condition.py:31 | entry | +| Local Variable y | b_condition.py:31 | exit | +| Local Variable y | b_condition.py:32 | exit | +| Local Variable y | b_condition.py:33 | entry | +| Local Variable y | b_condition.py:33 | exit | +| Local Variable y | b_condition.py:34 | entry | +| Local Variable y | b_condition.py:36 | entry | +| Local Variable y | b_condition.py:36 | exit | +| Local Variable y | b_condition.py:61 | entry | +| Local Variable y | b_condition.py:62 | exit | +| Local Variable y | b_condition.py:63 | entry | +| Local Variable y | b_condition.py:63 | exit | +| Local Variable y | b_condition.py:64 | entry | +| Local Variable y | b_condition.py:64 | exit | +| Local Variable y | b_condition.py:65 | entry | +| Local Variable y | b_condition.py:65 | exit | +| Local Variable y | b_condition.py:66 | entry | +| Local Variable y | b_condition.py:66 | exit | +| Local Variable y | b_condition.py:67 | entry | +| Local Variable y | b_condition.py:67 | exit | +| Local Variable y | b_condition.py:88 | entry | +| Local Variable y | b_condition.py:88 | exit | +| Local Variable y | b_condition.py:90 | entry | +| Local Variable y | b_condition.py:90 | exit | diff --git a/python/ql/test/library-tests/PointsTo/new/Live.ql b/python/ql/test/library-tests/PointsTo/new/Live.ql new file mode 100644 index 000000000000..ffd60fe5e6b8 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Live.ql @@ -0,0 +1,15 @@ + +import python +import semmle.dataflow.SSA +import semmle.dataflow.SsaCompute + +import Util + +from Variable var, BasicBlock b, ControlFlowNode loc, string end +where +Liveness::liveAtEntry(var, b) and end = "entry" and loc = b.getNode(0) +or +Liveness::liveAtExit(var, b) and end = "exit" and loc = b.getLastNode() + + +select var, locate(loc.getLocation(), "b"), end \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/new/NameSpace.expected b/python/ql/test/library-tests/PointsTo/new/NameSpace.expected new file mode 100644 index 000000000000..5f84595de0a8 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/NameSpace.expected @@ -0,0 +1,191 @@ +| a_simple.py:0 | Module code.a_simple | C | class C | +| a_simple.py:0 | Module code.a_simple | f | Function f | +| a_simple.py:0 | Module code.a_simple | f1 | float 1.0 | +| a_simple.py:0 | Module code.a_simple | func | Function func | +| a_simple.py:0 | Module code.a_simple | i1 | int 0 | +| a_simple.py:0 | Module code.a_simple | multi_loop | Function multi_loop | +| a_simple.py:0 | Module code.a_simple | multi_loop_in_try | Function multi_loop_in_try | +| a_simple.py:0 | Module code.a_simple | s | Tuple | +| a_simple.py:0 | Module code.a_simple | vararg_kwarg | Function vararg_kwarg | +| a_simple.py:0 | Module code.a_simple | with_definition | Function with_definition | +| b_condition.py:0 | Module code.b_condition | double_attr_check | Function double_attr_check | +| b_condition.py:0 | Module code.b_condition | f | Function f | +| b_condition.py:0 | Module code.b_condition | g | Function g | +| b_condition.py:0 | Module code.b_condition | h | Function h | +| b_condition.py:0 | Module code.b_condition | k | Function k | +| b_condition.py:0 | Module code.b_condition | loop | Function loop | +| b_condition.py:0 | Module code.b_condition | not_or_not | Function not_or_not | +| b_condition.py:0 | Module code.b_condition | odasa6261 | Function odasa6261 | +| b_condition.py:0 | Module code.b_condition | split_bool1 | Function split_bool1 | +| c_tests.py:0 | Module code.c_tests | complex_test | Function complex_test | +| c_tests.py:0 | Module code.c_tests | compound | Function compound | +| c_tests.py:0 | Module code.c_tests | f | Function f | +| c_tests.py:0 | Module code.c_tests | h | Function h | +| c_tests.py:0 | Module code.c_tests | others | Function others | +| d_globals.py:0 | Module code.d_globals | D | class D | +| d_globals.py:0 | Module code.d_globals | Ugly | class Ugly | +| d_globals.py:0 | Module code.d_globals | X | class X | +| d_globals.py:0 | Module code.d_globals | assign_global | Function assign_global | +| d_globals.py:0 | Module code.d_globals | dict | int 7 | +| d_globals.py:0 | Module code.d_globals | g1 | NoneType None | +| d_globals.py:0 | Module code.d_globals | g2 | int 102 | +| d_globals.py:0 | Module code.d_globals | g3 | NoneType None | +| d_globals.py:0 | Module code.d_globals | g4 | NoneType None | +| d_globals.py:0 | Module code.d_globals | get_g4 | Function get_g4 | +| d_globals.py:0 | Module code.d_globals | init | Function init | +| d_globals.py:0 | Module code.d_globals | j | Function j | +| d_globals.py:0 | Module code.d_globals | k | Function k | +| d_globals.py:0 | Module code.d_globals | outer | Function outer | +| d_globals.py:0 | Module code.d_globals | redefine | Function redefine | +| d_globals.py:0 | Module code.d_globals | set_g4 | Function set_g4 | +| d_globals.py:0 | Module code.d_globals | set_g4_indirect | Function set_g4_indirect | +| d_globals.py:0 | Module code.d_globals | tuple | builtin-class tuple | +| d_globals.py:0 | Module code.d_globals | use_list_attribute | Function use_list_attribute | +| d_globals.py:0 | Module code.d_globals | x | int 1 | +| d_globals.py:0 | Module code.d_globals | x | int 3 | +| d_globals.py:0 | Module code.d_globals | y | int 1 | +| d_globals.py:0 | Module code.d_globals | y | int 2 | +| d_globals.py:35 | Class Ugly | __init__ | Function __init__ | +| d_globals.py:35 | Class Ugly | meth | Function meth | +| d_globals.py:62 | Class X | y | int 1 | +| d_globals.py:62 | Class X | y | int 2 | +| d_globals.py:118 | Class D | __init__ | Function __init__ | +| d_globals.py:118 | Class D | foo | Function foo | +| g_class_init.py:0 | Module code.g_class_init | C | class C | +| g_class_init.py:0 | Module code.g_class_init | D | class D | +| g_class_init.py:0 | Module code.g_class_init | E | class E | +| g_class_init.py:0 | Module code.g_class_init | Oddities | class Oddities | +| g_class_init.py:0 | Module code.g_class_init | V2 | 'v2' | +| g_class_init.py:0 | Module code.g_class_init | V3 | 'v3' | +| g_class_init.py:3 | Class C | __init__ | Function __init__ | +| g_class_init.py:3 | Class C | _init | Function _init | +| g_class_init.py:3 | Class C | _init2 | Function _init2 | +| g_class_init.py:3 | Class C | method | Function method | +| g_class_init.py:24 | Class Oddities | float | builtin-class float | +| g_class_init.py:24 | Class Oddities | h | Builtin-function hash | +| g_class_init.py:24 | Class Oddities | int | builtin-class int | +| g_class_init.py:24 | Class Oddities | l | Builtin-function len | +| g_class_init.py:32 | Class D | __init__ | Function __init__ | +| g_class_init.py:45 | Class E | __init__ | Function __init__ | +| g_class_init.py:45 | Class E | meth | Function meth | +| h_classes.py:0 | Module code.h_classes | Base | class Base | +| h_classes.py:0 | Module code.h_classes | C | class C | +| h_classes.py:0 | Module code.h_classes | D | class D | +| h_classes.py:0 | Module code.h_classes | Derived1 | class Derived1 | +| h_classes.py:0 | Module code.h_classes | Derived2 | class Derived2 | +| h_classes.py:0 | Module code.h_classes | Derived3 | class Derived3 | +| h_classes.py:0 | Module code.h_classes | f | Function f | +| h_classes.py:0 | Module code.h_classes | k | Function k | +| h_classes.py:0 | Module code.h_classes | sys | Module sys | +| h_classes.py:3 | Class C | __init__ | Function __init__ | +| h_classes.py:3 | Class C | x | 'C_x' | +| h_classes.py:23 | Class Base | __init__ | Function __init__ | +| h_classes.py:48 | Class D | m | Function f | +| h_classes.py:48 | Class D | n | Function n | +| i_imports.py:0 | Module code.i_imports | BytesIO | builtin-class _io.BytesIO | +| i_imports.py:0 | Module code.i_imports | StringIO | builtin-class _io.StringIO | +| i_imports.py:0 | Module code.i_imports | _io | Module _io | +| i_imports.py:0 | Module code.i_imports | a | int 1 | +| i_imports.py:0 | Module code.i_imports | argv | list object | +| i_imports.py:0 | Module code.i_imports | b | int 2 | +| i_imports.py:0 | Module code.i_imports | c | int 3 | +| i_imports.py:0 | Module code.i_imports | code | Module code | +| i_imports.py:0 | Module code.i_imports | io | Module io | +| i_imports.py:0 | Module code.i_imports | module1 | Module code.test_package.module1 | +| i_imports.py:0 | Module code.i_imports | module2 | Module code.test_package.module2 | +| i_imports.py:0 | Module code.i_imports | p | int 1 | +| i_imports.py:0 | Module code.i_imports | q | int 2 | +| i_imports.py:0 | Module code.i_imports | r | Dict | +| i_imports.py:0 | Module code.i_imports | s | NoneType None | +| i_imports.py:0 | Module code.i_imports | sys | Module sys | +| i_imports.py:0 | Module code.i_imports | x | float 1.0 | +| i_imports.py:0 | Module code.i_imports | xyz | Module code.xyz | +| i_imports.py:0 | Module code.i_imports | y | float 2.0 | +| i_imports.py:0 | Module code.i_imports | z | float 3.0 | +| j_convoluted_imports.py:0 | Module code.j_convoluted_imports | C | class C | +| j_convoluted_imports.py:0 | Module code.j_convoluted_imports | module | Function module | +| j_convoluted_imports.py:0 | Module code.j_convoluted_imports | moduleX | Module code.package.moduleX | +| j_convoluted_imports.py:0 | Module code.j_convoluted_imports | x | Module code.package.x | +| j_convoluted_imports.py:9 | Class C | f | Function f | +| j_convoluted_imports.py:9 | Class C | module2 | int 7 | +| k_getsetattr.py:0 | Module code.k_getsetattr | C | class C | +| k_getsetattr.py:0 | Module code.k_getsetattr | k | Function k | +| k_getsetattr.py:4 | Class C | meth1 | Function meth1 | +| k_getsetattr.py:4 | Class C | meth2 | Function meth2 | +| l_calls.py:0 | Module code.l_calls | Owner | class Owner | +| l_calls.py:0 | Module code.l_calls | bar | Function bar | +| l_calls.py:0 | Module code.l_calls | foo | Function foo | +| l_calls.py:12 | Class Owner | cm | classmethod() | +| l_calls.py:12 | Class Owner | cm2 | classmethod() | +| l_calls.py:12 | Class Owner | m | Function m | +| o_no_returns.py:0 | Module code.o_no_returns | bar | Function bar | +| o_no_returns.py:0 | Module code.o_no_returns | fail | Function fail | +| o_no_returns.py:0 | Module code.o_no_returns | foo | Function foo | +| o_no_returns.py:0 | Module code.o_no_returns | sys | Module sys | +| p_decorators.py:0 | Module code.p_decorators | C | class C | +| p_decorators.py:0 | Module code.p_decorators | bar | Function bar | +| p_decorators.py:0 | Module code.p_decorators | complex | Function complex | +| p_decorators.py:0 | Module code.p_decorators | foo | Function foo | +| p_decorators.py:0 | Module code.p_decorators | simple | Function simple | +| p_decorators.py:24 | Class C | cmeth | classmethod() | +| p_decorators.py:24 | Class C | smeth | staticmethod() | +| q_super.py:0 | Module code.q_super | Base1 | class Base1 | +| q_super.py:0 | Module code.q_super | Base2 | class Base2 | +| q_super.py:0 | Module code.q_super | DA | class DA | +| q_super.py:0 | Module code.q_super | DB | class DB | +| q_super.py:0 | Module code.q_super | DD | class DD | +| q_super.py:0 | Module code.q_super | DE | class DE | +| q_super.py:0 | Module code.q_super | Derived1 | class Derived1 | +| q_super.py:0 | Module code.q_super | Derived2 | class Derived2 | +| q_super.py:0 | Module code.q_super | Derived4 | class Derived4 | +| q_super.py:0 | Module code.q_super | Derived5 | class Derived5 | +| q_super.py:0 | Module code.q_super | M | class M | +| q_super.py:0 | Module code.q_super | N | class N | +| q_super.py:0 | Module code.q_super | Wrong1 | class Wrong1 | +| q_super.py:1 | Class Base2 | __init__ | Function __init__ | +| q_super.py:8 | Class Derived4 | __init__ | Function __init__ | +| q_super.py:14 | Class Base1 | meth | Function meth | +| q_super.py:19 | Class Derived1 | meth | Function meth | +| q_super.py:24 | Class Derived2 | meth | Function meth | +| q_super.py:29 | Class Derived5 | meth | Function meth | +| q_super.py:35 | Class Wrong1 | meth | Function meth | +| q_super.py:41 | Class DA | __init__ | Function __init__ | +| q_super.py:46 | Class DB | DC | class DC | +| q_super.py:48 | Class DC | __init__ | Function __init__ | +| q_super.py:55 | Class DD | __init__ | Function __init__ | +| q_super.py:61 | Class DE | DF | class DF | +| q_super.py:63 | Class DF | __init__ | Function __init__ | +| q_super.py:71 | Class M | __init__ | Function __init__ | +| r_regressions.py:0 | Module code.r_regressions | C | class C | +| r_regressions.py:0 | Module code.r_regressions | Queue | class Queue | +| r_regressions.py:0 | Module code.r_regressions | TestFirst | class TestFirst | +| r_regressions.py:0 | Module code.r_regressions | _names | tuple object | +| r_regressions.py:0 | Module code.r_regressions | deco | Function deco | +| r_regressions.py:0 | Module code.r_regressions | f | Function f | +| r_regressions.py:0 | Module code.r_regressions | fail | Function fail | +| r_regressions.py:0 | Module code.r_regressions | find_library | Function find_library | +| r_regressions.py:0 | Module code.r_regressions | method_decorator | Function method_decorator | +| r_regressions.py:0 | Module code.r_regressions | sys | Module sys | +| r_regressions.py:0 | Module code.r_regressions | t | Module time | +| r_regressions.py:5 | Class Queue | __init__ | Function __init__ | +| r_regressions.py:5 | Class Queue | _after_fork | Function _after_fork | +| r_regressions.py:5 | Class Queue | close | Function close | +| r_regressions.py:49 | Class C | fail | Function fail | +| r_regressions.py:86 | Class TestFirst | method | Function method | +| s_scopes.py:0 | Module code.s_scopes | C2 | class C2 | +| s_scopes.py:0 | Module code.s_scopes | f | bool True | +| s_scopes.py:0 | Module code.s_scopes | f | builtin-class float | +| s_scopes.py:0 | Module code.s_scopes | float | bool True | +| s_scopes.py:0 | Module code.s_scopes | i | builtin-class int | +| s_scopes.py:7 | Class C2 | f1 | bool True | +| s_scopes.py:7 | Class C2 | f1 | builtin-class float | +| s_scopes.py:7 | Class C2 | f2 | NoneType None | +| s_scopes.py:7 | Class C2 | f2 | bool True | +| s_scopes.py:7 | Class C2 | f2 | builtin-class float | +| s_scopes.py:7 | Class C2 | float | NoneType None | +| s_scopes.py:7 | Class C2 | i1 | builtin-class int | +| s_scopes.py:7 | Class C2 | i2 | int 0 | +| s_scopes.py:7 | Class C2 | int | int 0 | +| s_scopes.py:7 | Class C2 | s | builtin-class str | +| s_scopes.py:7 | Class C2 | s | float 1.0 | +| s_scopes.py:7 | Class C2 | str | float 1.0 | diff --git a/python/ql/test/library-tests/PointsTo/new/NameSpace.ql b/python/ql/test/library-tests/PointsTo/new/NameSpace.ql new file mode 100644 index 000000000000..4e30796dc0b6 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/NameSpace.ql @@ -0,0 +1,18 @@ +import python +import Util + +from Scope s, string name, Object val +where name != "__name__" and +( + exists(ModuleObject m | + m.getModule() = s and + m.attributeRefersTo(name, val, _) + ) + or + exists(ClassObject cls | + cls.getPyClass() = s and + cls.declaredAttribute(name) = val + ) +) + +select locate(s.getLocation(), "abcdghijklopqrs"), s.toString(), name, repr(val) \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/new/Parameters.expected b/python/ql/test/library-tests/PointsTo/new/Parameters.expected new file mode 100644 index 000000000000..698705160077 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Parameters.expected @@ -0,0 +1,8 @@ +| g_class_init.py:5 | Essa node definition | true | +| g_class_init.py:9 | Essa node definition | true | +| g_class_init.py:13 | Essa node definition | true | +| g_class_init.py:16 | Essa node definition | true | +| g_class_init.py:34 | Essa node definition | true | +| g_class_init.py:46 | Essa node definition | false | +| g_class_init.py:46 | Essa node definition | true | +| g_class_init.py:52 | Essa node definition | true | diff --git a/python/ql/test/library-tests/PointsTo/new/Parameters.ql b/python/ql/test/library-tests/PointsTo/new/Parameters.ql new file mode 100644 index 000000000000..e3a76f9dc703 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Parameters.ql @@ -0,0 +1,10 @@ + +import python + +import Util + +from ParameterDefinition param, boolean self +where +if param.isSelf() then self = true else self = false + +select locate(param.getLocation(), "g"), param.toString(), self diff --git a/python/ql/test/library-tests/PointsTo/new/PointsToNone.expected b/python/ql/test/library-tests/PointsTo/new/PointsToNone.expected new file mode 100644 index 000000000000..f5f838edd8a5 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/PointsToNone.expected @@ -0,0 +1,98 @@ +| a_simple.py:19 | ControlFlowNode for None | 19 | +| a_simple.py:19 | ControlFlowNode for x | 19 | +| b_condition.py:5 | ControlFlowNode for IfExp | 5 | +| b_condition.py:5 | ControlFlowNode for None | 5 | +| b_condition.py:5 | ControlFlowNode for x | 5 | +| b_condition.py:7 | ControlFlowNode for None | 7 | +| b_condition.py:7 | ControlFlowNode for x | 5 | +| b_condition.py:11 | ControlFlowNode for IfExp | 11 | +| b_condition.py:11 | ControlFlowNode for None | 11 | +| b_condition.py:11 | ControlFlowNode for x | 11 | +| b_condition.py:13 | ControlFlowNode for None | 13 | +| b_condition.py:13 | ControlFlowNode for x | 11 | +| b_condition.py:15 | ControlFlowNode for x | 11 | +| b_condition.py:17 | ControlFlowNode for IfExp | 17 | +| b_condition.py:17 | ControlFlowNode for None | 17 | +| b_condition.py:17 | ControlFlowNode for x | 17 | +| b_condition.py:19 | ControlFlowNode for x | 17 | +| b_condition.py:20 | ControlFlowNode for None | 20 | +| b_condition.py:20 | ControlFlowNode for x | 20 | +| b_condition.py:21 | ControlFlowNode for x | 20 | +| b_condition.py:23 | ControlFlowNode for IfExp | 23 | +| b_condition.py:23 | ControlFlowNode for None | 23 | +| b_condition.py:23 | ControlFlowNode for x | 23 | +| b_condition.py:25 | ControlFlowNode for x | 23 | +| b_condition.py:42 | ControlFlowNode for None | 42 | +| b_condition.py:87 | ControlFlowNode for None | 87 | +| b_condition.py:88 | ControlFlowNode for x | 87 | +| b_condition.py:88 | ControlFlowNode for y | 87 | +| b_condition.py:90 | ControlFlowNode for x | 87 | +| b_condition.py:90 | ControlFlowNode for y | 87 | +| b_condition.py:92 | ControlFlowNode for x | 87 | +| b_condition.py:93 | ControlFlowNode for y | 87 | +| b_condition.py:96 | ControlFlowNode for y | 87 | +| b_condition.py:97 | ControlFlowNode for x | 87 | +| c_tests.py:5 | ControlFlowNode for IfExp | 5 | +| c_tests.py:5 | ControlFlowNode for None | 5 | +| c_tests.py:5 | ControlFlowNode for x | 5 | +| c_tests.py:7 | ControlFlowNode for None | 7 | +| c_tests.py:7 | ControlFlowNode for x | 5 | +| c_tests.py:32 | ControlFlowNode for Attribute | 32 | +| c_tests.py:32 | ControlFlowNode for IfExp | 32 | +| c_tests.py:32 | ControlFlowNode for None | 32 | +| c_tests.py:34 | ControlFlowNode for Attribute | 32 | +| c_tests.py:34 | ControlFlowNode for None | 34 | +| c_tests.py:90 | ControlFlowNode for IfExp | 90 | +| c_tests.py:90 | ControlFlowNode for None | 90 | +| c_tests.py:90 | ControlFlowNode for x | 90 | +| c_tests.py:91 | ControlFlowNode for x | 90 | +| c_tests.py:94 | ControlFlowNode for IfExp | 94 | +| c_tests.py:94 | ControlFlowNode for None | 94 | +| c_tests.py:94 | ControlFlowNode for x | 94 | +| c_tests.py:95 | ControlFlowNode for x | 94 | +| d_globals.py:14 | ControlFlowNode for None | 14 | +| d_globals.py:14 | ControlFlowNode for g1 | 14 | +| d_globals.py:23 | ControlFlowNode for None | 23 | +| d_globals.py:23 | ControlFlowNode for g2 | 23 | +| d_globals.py:29 | ControlFlowNode for init() | 25 | +| d_globals.py:33 | ControlFlowNode for None | 33 | +| d_globals.py:33 | ControlFlowNode for g3 | 33 | +| d_globals.py:66 | ControlFlowNode for g3 | 33 | +| d_globals.py:73 | ControlFlowNode for None | 73 | +| d_globals.py:73 | ControlFlowNode for g4 | 73 | +| d_globals.py:76 | ControlFlowNode for g4 | 73 | +| d_globals.py:77 | ControlFlowNode for set_g4() | 80 | +| d_globals.py:81 | ControlFlowNode for set_g4_indirect() | 83 | +| d_globals.py:128 | ControlFlowNode for Attribute() | 128 | +| g_class_init.py:6 | ControlFlowNode for Attribute() | 9 | +| g_class_init.py:11 | ControlFlowNode for Attribute() | 13 | +| i_imports.py:38 | ControlFlowNode for Attribute() | 24 | +| k_getsetattr.py:7 | ControlFlowNode for setattr() | 7 | +| k_getsetattr.py:8 | ControlFlowNode for setattr() | 8 | +| k_getsetattr.py:13 | ControlFlowNode for setattr() | 13 | +| k_getsetattr.py:14 | ControlFlowNode for setattr() | 14 | +| k_getsetattr.py:15 | ControlFlowNode for Attribute() | 6 | +| l_calls.py:4 | ControlFlowNode for Attribute() | 4 | +| l_calls.py:9 | ControlFlowNode for foo() | 4 | +| m_attributes.py:12 | ControlFlowNode for Attribute() | 8 | +| m_attributes.py:13 | ControlFlowNode for Attribute() | 8 | +| o_no_returns.py:7 | ControlFlowNode for fail() | 10 | +| o_no_returns.py:15 | ControlFlowNode for bar() | 5 | +| o_no_returns.py:21 | ControlFlowNode for bar() | 5 | +| q_super.py:12 | ControlFlowNode for Attribute() | 3 | +| q_super.py:52 | ControlFlowNode for Attribute() | 43 | +| q_super.py:59 | ControlFlowNode for Attribute() | 43 | +| q_super.py:66 | ControlFlowNode for Attribute() | 43 | +| r_regressions.py:9 | ControlFlowNode for Attribute() | 11 | +| r_regressions.py:13 | ControlFlowNode for Attribute | 13 | +| r_regressions.py:13 | ControlFlowNode for None | 13 | +| r_regressions.py:20 | ControlFlowNode for Attribute | 13 | +| r_regressions.py:20 | ControlFlowNode for close | 13 | +| r_regressions.py:21 | ControlFlowNode for close | 13 | +| r_regressions.py:22 | ControlFlowNode for Attribute | 22 | +| r_regressions.py:22 | ControlFlowNode for None | 22 | +| r_regressions.py:27 | ControlFlowNode for None | 27 | +| r_regressions.py:31 | ControlFlowNode for y | 27 | +| r_regressions.py:33 | ControlFlowNode for y | 27 | +| r_regressions.py:52 | ControlFlowNode for fail() | 46 | +| r_regressions.py:73 | ControlFlowNode for setattr() | 73 | diff --git a/python/ql/test/library-tests/PointsTo/new/PointsToNone.ql b/python/ql/test/library-tests/PointsTo/new/PointsToNone.ql new file mode 100644 index 000000000000..3bebd98bff1a --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/PointsToNone.ql @@ -0,0 +1,9 @@ +import python +import Util + +from ControlFlowNode f, ControlFlowNode x + +where +f.refersTo(theNoneObject(), _, x) + +select locate(f.getLocation(), "abcdghijklmopqr"), f.toString(), x.getLocation().getStartLine() diff --git a/python/ql/test/library-tests/PointsTo/new/PointsToUnknown.expected b/python/ql/test/library-tests/PointsTo/new/PointsToUnknown.expected new file mode 100644 index 000000000000..0529792ac076 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/PointsToUnknown.expected @@ -0,0 +1,227 @@ +| a_simple.py:15 | ControlFlowNode for t | 14 | +| a_simple.py:16 | ControlFlowNode for d | 14 | +| a_simple.py:20 | ControlFlowNode for seq | 18 | +| a_simple.py:24 | ControlFlowNode for x | 23 | +| a_simple.py:29 | ControlFlowNode for x | 27 | +| a_simple.py:35 | ControlFlowNode for Subscript | 35 | +| a_simple.py:35 | ControlFlowNode for args | 34 | +| a_simple.py:36 | ControlFlowNode for Subscript | 36 | +| a_simple.py:36 | ControlFlowNode for kwargs | 34 | +| b_condition.py:5 | ControlFlowNode for IfExp | 5 | +| b_condition.py:5 | ControlFlowNode for cond | 5 | +| b_condition.py:5 | ControlFlowNode for unknown | 5 | +| b_condition.py:5 | ControlFlowNode for unknown() | 5 | +| b_condition.py:5 | ControlFlowNode for x | 5 | +| b_condition.py:7 | ControlFlowNode for x | 5 | +| b_condition.py:9 | ControlFlowNode for use | 9 | +| b_condition.py:9 | ControlFlowNode for use() | 9 | +| b_condition.py:9 | ControlFlowNode for x | 5 | +| b_condition.py:11 | ControlFlowNode for IfExp | 11 | +| b_condition.py:11 | ControlFlowNode for cond | 11 | +| b_condition.py:11 | ControlFlowNode for unknown | 11 | +| b_condition.py:11 | ControlFlowNode for unknown() | 11 | +| b_condition.py:11 | ControlFlowNode for x | 11 | +| b_condition.py:13 | ControlFlowNode for x | 11 | +| b_condition.py:15 | ControlFlowNode for use | 15 | +| b_condition.py:15 | ControlFlowNode for use() | 15 | +| b_condition.py:15 | ControlFlowNode for x | 11 | +| b_condition.py:17 | ControlFlowNode for IfExp | 17 | +| b_condition.py:17 | ControlFlowNode for cond | 17 | +| b_condition.py:17 | ControlFlowNode for unknown | 17 | +| b_condition.py:17 | ControlFlowNode for unknown() | 17 | +| b_condition.py:17 | ControlFlowNode for x | 17 | +| b_condition.py:19 | ControlFlowNode for x | 17 | +| b_condition.py:21 | ControlFlowNode for use | 21 | +| b_condition.py:21 | ControlFlowNode for use() | 21 | +| b_condition.py:21 | ControlFlowNode for x | 17 | +| b_condition.py:23 | ControlFlowNode for IfExp | 23 | +| b_condition.py:23 | ControlFlowNode for cond | 23 | +| b_condition.py:23 | ControlFlowNode for unknown | 23 | +| b_condition.py:23 | ControlFlowNode for unknown() | 23 | +| b_condition.py:23 | ControlFlowNode for x | 23 | +| b_condition.py:25 | ControlFlowNode for IfExp | 23 | +| b_condition.py:25 | ControlFlowNode for x | 23 | +| b_condition.py:26 | ControlFlowNode for use | 26 | +| b_condition.py:26 | ControlFlowNode for use() | 26 | +| b_condition.py:26 | ControlFlowNode for x | 23 | +| b_condition.py:27 | ControlFlowNode for unknown | 27 | +| b_condition.py:27 | ControlFlowNode for unknown() | 27 | +| b_condition.py:29 | ControlFlowNode for use | 29 | +| b_condition.py:29 | ControlFlowNode for use() | 29 | +| b_condition.py:29 | ControlFlowNode for x | 23 | +| b_condition.py:31 | ControlFlowNode for IfExp | 31 | +| b_condition.py:31 | ControlFlowNode for cond | 31 | +| b_condition.py:31 | ControlFlowNode for unknown | 31 | +| b_condition.py:31 | ControlFlowNode for unknown() | 31 | +| b_condition.py:31 | ControlFlowNode for x | 31 | +| b_condition.py:32 | ControlFlowNode for x | 31 | +| b_condition.py:34 | ControlFlowNode for use | 34 | +| b_condition.py:34 | ControlFlowNode for use() | 34 | +| b_condition.py:34 | ControlFlowNode for x | 31 | +| b_condition.py:36 | ControlFlowNode for x | 31 | +| b_condition.py:37 | ControlFlowNode for use | 37 | +| b_condition.py:37 | ControlFlowNode for use() | 37 | +| b_condition.py:37 | ControlFlowNode for x | 31 | +| b_condition.py:39 | ControlFlowNode for thing | 39 | +| b_condition.py:39 | ControlFlowNode for thing() | 39 | +| b_condition.py:39 | ControlFlowNode for v2 | 39 | +| b_condition.py:41 | ControlFlowNode for Attribute | 39 | +| b_condition.py:41 | ControlFlowNode for v2 | 39 | +| b_condition.py:42 | ControlFlowNode for Attribute | 39 | +| b_condition.py:42 | ControlFlowNode for v2 | 39 | +| b_condition.py:43 | ControlFlowNode for Attribute | 39 | +| b_condition.py:43 | ControlFlowNode for use | 43 | +| b_condition.py:43 | ControlFlowNode for use() | 43 | +| b_condition.py:43 | ControlFlowNode for v2 | 39 | +| b_condition.py:44 | ControlFlowNode for Attribute | 39 | +| b_condition.py:44 | ControlFlowNode for use | 44 | +| b_condition.py:44 | ControlFlowNode for use() | 44 | +| b_condition.py:44 | ControlFlowNode for v2 | 39 | +| b_condition.py:51 | ControlFlowNode for x | 50 | +| b_condition.py:52 | ControlFlowNode for x | 50 | +| b_condition.py:56 | ControlFlowNode for seq | 55 | +| b_condition.py:57 | ControlFlowNode for v | 56 | +| b_condition.py:58 | ControlFlowNode for use | 58 | +| b_condition.py:58 | ControlFlowNode for use() | 58 | +| b_condition.py:58 | ControlFlowNode for v | 56 | +| b_condition.py:62 | ControlFlowNode for Attribute | 61 | +| b_condition.py:62 | ControlFlowNode for x | 61 | +| b_condition.py:64 | ControlFlowNode for y | 61 | +| b_condition.py:65 | ControlFlowNode for Attribute | 61 | +| b_condition.py:65 | ControlFlowNode for x | 61 | +| b_condition.py:66 | ControlFlowNode for Attribute | 61 | +| b_condition.py:66 | ControlFlowNode for seq | 66 | +| b_condition.py:66 | ControlFlowNode for x | 61 | +| b_condition.py:70 | ControlFlowNode for IfExp | 70 | +| b_condition.py:70 | ControlFlowNode for b | 70 | +| b_condition.py:70 | ControlFlowNode for cond | 70 | +| b_condition.py:70 | ControlFlowNode for unknown | 70 | +| b_condition.py:70 | ControlFlowNode for unknown() | 70 | +| b_condition.py:71 | ControlFlowNode for b | 70 | +| b_condition.py:73 | ControlFlowNode for b | 70 | +| b_condition.py:79 | ControlFlowNode for use | 79 | +| b_condition.py:79 | ControlFlowNode for use() | 79 | +| b_condition.py:82 | ControlFlowNode for foo | 81 | +| b_condition.py:88 | ControlFlowNode for x | 87 | +| b_condition.py:88 | ControlFlowNode for y | 87 | +| b_condition.py:90 | ControlFlowNode for x | 87 | +| b_condition.py:90 | ControlFlowNode for y | 87 | +| b_condition.py:93 | ControlFlowNode for use | 93 | +| b_condition.py:93 | ControlFlowNode for use() | 93 | +| b_condition.py:93 | ControlFlowNode for y | 87 | +| b_condition.py:95 | ControlFlowNode for use | 95 | +| b_condition.py:95 | ControlFlowNode for use() | 95 | +| b_condition.py:95 | ControlFlowNode for y | 87 | +| b_condition.py:96 | ControlFlowNode for y | 87 | +| b_condition.py:97 | ControlFlowNode for use | 97 | +| b_condition.py:97 | ControlFlowNode for use() | 97 | +| b_condition.py:99 | ControlFlowNode for use | 99 | +| b_condition.py:99 | ControlFlowNode for use() | 99 | +| b_condition.py:102 | ControlFlowNode for a | 101 | +| b_condition.py:104 | ControlFlowNode for a | 101 | +| b_condition.py:105 | ControlFlowNode for Subscript | 105 | +| b_condition.py:105 | ControlFlowNode for a | 101 | +| c_tests.py:5 | ControlFlowNode for IfExp | 5 | +| c_tests.py:5 | ControlFlowNode for cond | 5 | +| c_tests.py:5 | ControlFlowNode for unknown | 5 | +| c_tests.py:5 | ControlFlowNode for unknown() | 5 | +| c_tests.py:5 | ControlFlowNode for x | 5 | +| c_tests.py:7 | ControlFlowNode for x | 5 | +| c_tests.py:10 | ControlFlowNode for cond | 10 | +| c_tests.py:15 | ControlFlowNode for cond | 15 | +| c_tests.py:21 | ControlFlowNode for cond | 21 | +| c_tests.py:21 | ControlFlowNode for unknown | 21 | +| c_tests.py:21 | ControlFlowNode for unknown() | 21 | +| c_tests.py:32 | ControlFlowNode for Attribute | 4 | +| c_tests.py:32 | ControlFlowNode for Attribute | 32 | +| c_tests.py:32 | ControlFlowNode for IfExp | 32 | +| c_tests.py:32 | ControlFlowNode for cond | 32 | +| c_tests.py:32 | ControlFlowNode for unknown | 32 | +| c_tests.py:32 | ControlFlowNode for unknown() | 32 | +| c_tests.py:32 | ControlFlowNode for y | 4 | +| c_tests.py:34 | ControlFlowNode for Attribute | 4 | +| c_tests.py:34 | ControlFlowNode for Attribute | 32 | +| c_tests.py:34 | ControlFlowNode for y | 4 | +| c_tests.py:37 | ControlFlowNode for Attribute | 4 | +| c_tests.py:37 | ControlFlowNode for cond | 37 | +| c_tests.py:37 | ControlFlowNode for y | 4 | +| c_tests.py:39 | ControlFlowNode for Attribute | 4 | +| c_tests.py:39 | ControlFlowNode for y | 4 | +| c_tests.py:42 | ControlFlowNode for Attribute | 4 | +| c_tests.py:42 | ControlFlowNode for cond | 42 | +| c_tests.py:42 | ControlFlowNode for y | 4 | +| c_tests.py:44 | ControlFlowNode for Attribute | 4 | +| c_tests.py:44 | ControlFlowNode for y | 4 | +| c_tests.py:48 | ControlFlowNode for Attribute | 4 | +| c_tests.py:48 | ControlFlowNode for cond | 48 | +| c_tests.py:48 | ControlFlowNode for unknown | 48 | +| c_tests.py:48 | ControlFlowNode for unknown() | 48 | +| c_tests.py:48 | ControlFlowNode for y | 4 | +| c_tests.py:50 | ControlFlowNode for Attribute | 4 | +| c_tests.py:50 | ControlFlowNode for y | 4 | +| c_tests.py:53 | ControlFlowNode for Attribute | 4 | +| c_tests.py:53 | ControlFlowNode for y | 4 | +| c_tests.py:58 | ControlFlowNode for cond | 58 | +| c_tests.py:63 | ControlFlowNode for cond | 63 | +| c_tests.py:73 | ControlFlowNode for x | 71 | +| c_tests.py:73 | ControlFlowNode for y | 71 | +| c_tests.py:74 | ControlFlowNode for x | 71 | +| c_tests.py:74 | ControlFlowNode for y | 71 | +| c_tests.py:76 | ControlFlowNode for x | 71 | +| c_tests.py:76 | ControlFlowNode for y | 71 | +| c_tests.py:77 | ControlFlowNode for x | 71 | +| c_tests.py:77 | ControlFlowNode for y | 71 | +| c_tests.py:80 | ControlFlowNode for IfExp | 80 | +| c_tests.py:80 | ControlFlowNode for b | 80 | +| c_tests.py:80 | ControlFlowNode for cond | 80 | +| c_tests.py:80 | ControlFlowNode for unknown | 80 | +| c_tests.py:80 | ControlFlowNode for unknown() | 80 | +| c_tests.py:81 | ControlFlowNode for b | 80 | +| c_tests.py:83 | ControlFlowNode for IfExp | 83 | +| c_tests.py:83 | ControlFlowNode for b | 83 | +| c_tests.py:83 | ControlFlowNode for cond | 83 | +| c_tests.py:83 | ControlFlowNode for unknown | 83 | +| c_tests.py:83 | ControlFlowNode for unknown() | 83 | +| c_tests.py:84 | ControlFlowNode for b | 83 | +| c_tests.py:87 | ControlFlowNode for unknown | 87 | +| c_tests.py:87 | ControlFlowNode for unknown() | 87 | +| c_tests.py:90 | ControlFlowNode for IfExp | 90 | +| c_tests.py:90 | ControlFlowNode for cond | 90 | +| c_tests.py:90 | ControlFlowNode for unknown | 90 | +| c_tests.py:90 | ControlFlowNode for unknown() | 90 | +| c_tests.py:90 | ControlFlowNode for x | 90 | +| c_tests.py:91 | ControlFlowNode for x | 90 | +| c_tests.py:94 | ControlFlowNode for IfExp | 94 | +| c_tests.py:94 | ControlFlowNode for cond | 94 | +| c_tests.py:94 | ControlFlowNode for unknown | 94 | +| c_tests.py:94 | ControlFlowNode for unknown() | 94 | +| c_tests.py:94 | ControlFlowNode for x | 94 | +| c_tests.py:95 | ControlFlowNode for x | 94 | +| c_tests.py:99 | ControlFlowNode for bar | 99 | +| c_tests.py:99 | ControlFlowNode for bar() | 99 | +| c_tests.py:99 | ControlFlowNode for foo | 99 | +| c_tests.py:99 | ControlFlowNode for foo() | 99 | +| c_tests.py:99 | ControlFlowNode for x | 98 | +| c_tests.py:100 | ControlFlowNode for use | 100 | +| c_tests.py:100 | ControlFlowNode for use() | 100 | +| c_tests.py:100 | ControlFlowNode for x | 98 | +| h_classes.py:12 | ControlFlowNode for name | 12 | +| h_classes.py:17 | ControlFlowNode for arg | 14 | +| h_classes.py:18 | ControlFlowNode for name | 18 | +| h_classes.py:26 | ControlFlowNode for choice | 25 | +| h_classes.py:28 | ControlFlowNode for choice | 25 | +| h_classes.py:42 | ControlFlowNode for unknown | 42 | +| h_classes.py:42 | ControlFlowNode for unknown() | 42 | +| r_regressions.py:29 | ControlFlowNode for x | 27 | +| r_regressions.py:31 | ControlFlowNode for y | 27 | +| r_regressions.py:33 | ControlFlowNode for y | 27 | +| r_regressions.py:36 | ControlFlowNode for z | 27 | +| r_regressions.py:39 | ControlFlowNode for use | 39 | +| r_regressions.py:39 | ControlFlowNode for use() | 39 | +| r_regressions.py:39 | ControlFlowNode for y | 27 | +| r_regressions.py:43 | ControlFlowNode for List | 43 | +| r_regressions.py:43 | ControlFlowNode for x | 43 | +| r_regressions.py:43 | ControlFlowNode for x() | 43 | +| r_regressions.py:52 | ControlFlowNode for msg | 51 | +| r_regressions.py:64 | ControlFlowNode for do_validation | 64 | +| r_regressions.py:64 | ControlFlowNode for do_validation() | 64 | diff --git a/python/ql/test/library-tests/PointsTo/new/PointsToUnknown.ql b/python/ql/test/library-tests/PointsTo/new/PointsToUnknown.ql new file mode 100644 index 000000000000..e8258bc53a3f --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/PointsToUnknown.ql @@ -0,0 +1,9 @@ +import python +import Util +import semmle.python.pointsto.PointsTo + +from ControlFlowNode f, ControlFlowNode x + +where PointsTo::points_to(f, _, unknownValue(), _, x) + +select locate(f.getLocation(), "abchr"), f.toString(), x.getLocation().getStartLine() diff --git a/python/ql/test/library-tests/PointsTo/new/PointsToWithContext.expected b/python/ql/test/library-tests/PointsTo/new/PointsToWithContext.expected new file mode 100755 index 000000000000..80ac9fb72c9d --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/PointsToWithContext.expected @@ -0,0 +1,1111 @@ +| a_simple.py:2 | ControlFlowNode for FloatLiteral | float 1.0 | builtin-class float | 2 | import | +| a_simple.py:2 | ControlFlowNode for f1 | float 1.0 | builtin-class float | 2 | import | +| a_simple.py:3 | ControlFlowNode for dict | builtin-class dict | builtin-class type | 3 | import | +| a_simple.py:4 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type | 4 | import | +| a_simple.py:5 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 5 | import | +| a_simple.py:5 | ControlFlowNode for i1 | int 0 | builtin-class int | 5 | import | +| a_simple.py:6 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 6 | import | +| a_simple.py:6 | ControlFlowNode for s | Tuple | builtin-class tuple | 6 | import | +| a_simple.py:8 | ControlFlowNode for FunctionExpr | Function func | builtin-class function | 8 | import | +| a_simple.py:8 | ControlFlowNode for func | Function func | builtin-class function | 8 | import | +| a_simple.py:11 | ControlFlowNode for C | class C | builtin-class type | 11 | import | +| a_simple.py:11 | ControlFlowNode for ClassExpr | class C | builtin-class type | 11 | import | +| a_simple.py:11 | ControlFlowNode for object | builtin-class object | builtin-class type | 11 | import | +| a_simple.py:14 | ControlFlowNode for FunctionExpr | Function vararg_kwarg | builtin-class function | 14 | import | +| a_simple.py:14 | ControlFlowNode for d | d | builtin-class dict | 14 | runtime | +| a_simple.py:14 | ControlFlowNode for t | t | builtin-class tuple | 14 | runtime | +| a_simple.py:14 | ControlFlowNode for vararg_kwarg | Function vararg_kwarg | builtin-class function | 14 | import | +| a_simple.py:15 | ControlFlowNode for t | t | builtin-class tuple | 14 | runtime | +| a_simple.py:16 | ControlFlowNode for d | d | builtin-class dict | 14 | runtime | +| a_simple.py:18 | ControlFlowNode for FunctionExpr | Function multi_loop | builtin-class function | 18 | import | +| a_simple.py:18 | ControlFlowNode for multi_loop | Function multi_loop | builtin-class function | 18 | import | +| a_simple.py:19 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 19 | runtime | +| a_simple.py:19 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 19 | runtime | +| a_simple.py:20 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 20 | runtime | +| a_simple.py:23 | ControlFlowNode for FunctionExpr | Function with_definition | builtin-class function | 23 | import | +| a_simple.py:23 | ControlFlowNode for with_definition | Function with_definition | builtin-class function | 23 | import | +| a_simple.py:27 | ControlFlowNode for FunctionExpr | Function multi_loop_in_try | builtin-class function | 27 | import | +| a_simple.py:27 | ControlFlowNode for multi_loop_in_try | Function multi_loop_in_try | builtin-class function | 27 | import | +| a_simple.py:29 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 29 | runtime | +| a_simple.py:31 | ControlFlowNode for KeyError | builtin-class KeyError | builtin-class type | 31 | runtime | +| a_simple.py:34 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 34 | import | +| a_simple.py:34 | ControlFlowNode for args | args | builtin-class tuple | 34 | runtime | +| a_simple.py:34 | ControlFlowNode for f | Function f | builtin-class function | 34 | import | +| a_simple.py:34 | ControlFlowNode for kwargs | kwargs | builtin-class dict | 34 | runtime | +| a_simple.py:35 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 35 | runtime | +| a_simple.py:35 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 35 | runtime | +| a_simple.py:35 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 35 | runtime | +| a_simple.py:35 | ControlFlowNode for args | args | builtin-class tuple | 34 | runtime | +| a_simple.py:36 | ControlFlowNode for Str | 'x' | builtin-class str | 36 | runtime | +| a_simple.py:36 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 36 | runtime | +| a_simple.py:36 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 36 | runtime | +| a_simple.py:36 | ControlFlowNode for kwargs | kwargs | builtin-class dict | 34 | runtime | +| b_condition.py:4 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 4 | import | +| b_condition.py:4 | ControlFlowNode for f | Function f | builtin-class function | 4 | import | +| b_condition.py:5 | ControlFlowNode for IfExp | NoneType None | builtin-class NoneType | 5 | runtime | +| b_condition.py:5 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 5 | runtime | +| b_condition.py:5 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 5 | runtime | +| b_condition.py:7 | ControlFlowNode for Compare | bool False | builtin-class bool | 7 | runtime | +| b_condition.py:7 | ControlFlowNode for Compare | bool True | builtin-class bool | 7 | runtime | +| b_condition.py:7 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 7 | runtime | +| b_condition.py:7 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 5 | runtime | +| b_condition.py:8 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 8 | runtime | +| b_condition.py:8 | ControlFlowNode for x | int 7 | builtin-class int | 8 | runtime | +| b_condition.py:9 | ControlFlowNode for x | int 7 | builtin-class int | 8 | runtime | +| b_condition.py:11 | ControlFlowNode for IfExp | NoneType None | builtin-class NoneType | 11 | runtime | +| b_condition.py:11 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 11 | runtime | +| b_condition.py:11 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 11 | runtime | +| b_condition.py:13 | ControlFlowNode for Compare | bool False | builtin-class bool | 13 | runtime | +| b_condition.py:13 | ControlFlowNode for Compare | bool True | builtin-class bool | 13 | runtime | +| b_condition.py:13 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 13 | runtime | +| b_condition.py:13 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 11 | runtime | +| b_condition.py:14 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 14 | runtime | +| b_condition.py:14 | ControlFlowNode for x | int 7 | builtin-class int | 14 | runtime | +| b_condition.py:15 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 11 | runtime | +| b_condition.py:15 | ControlFlowNode for x | int 7 | builtin-class int | 14 | runtime | +| b_condition.py:17 | ControlFlowNode for IfExp | NoneType None | builtin-class NoneType | 17 | runtime | +| b_condition.py:17 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 17 | runtime | +| b_condition.py:17 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 17 | runtime | +| b_condition.py:19 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 19 | runtime | +| b_condition.py:19 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 19 | runtime | +| b_condition.py:19 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 17 | runtime | +| b_condition.py:20 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 20 | runtime | +| b_condition.py:20 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 20 | runtime | +| b_condition.py:21 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 20 | runtime | +| b_condition.py:23 | ControlFlowNode for IfExp | NoneType None | builtin-class NoneType | 23 | runtime | +| b_condition.py:23 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 23 | runtime | +| b_condition.py:23 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 23 | runtime | +| b_condition.py:25 | ControlFlowNode for IfExp | int 1 | builtin-class int | 25 | runtime | +| b_condition.py:25 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 25 | runtime | +| b_condition.py:25 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 23 | runtime | +| b_condition.py:25 | ControlFlowNode for x | int 1 | builtin-class int | 25 | runtime | +| b_condition.py:26 | ControlFlowNode for x | int 1 | builtin-class int | 25 | runtime | +| b_condition.py:28 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 28 | runtime | +| b_condition.py:28 | ControlFlowNode for x | int 1 | builtin-class int | 28 | runtime | +| b_condition.py:29 | ControlFlowNode for x | int 1 | builtin-class int | 25 | runtime | +| b_condition.py:29 | ControlFlowNode for x | int 1 | builtin-class int | 28 | runtime | +| b_condition.py:31 | ControlFlowNode for IfExp | int 1 | builtin-class int | 31 | runtime | +| b_condition.py:31 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 31 | runtime | +| b_condition.py:31 | ControlFlowNode for x | int 1 | builtin-class int | 31 | runtime | +| b_condition.py:32 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 32 | runtime | +| b_condition.py:32 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 32 | runtime | +| b_condition.py:32 | ControlFlowNode for x | int 1 | builtin-class int | 31 | runtime | +| b_condition.py:33 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 33 | runtime | +| b_condition.py:33 | ControlFlowNode for x | int 7 | builtin-class int | 33 | runtime | +| b_condition.py:34 | ControlFlowNode for x | int 1 | builtin-class int | 31 | runtime | +| b_condition.py:34 | ControlFlowNode for x | int 7 | builtin-class int | 33 | runtime | +| b_condition.py:36 | ControlFlowNode for int | builtin-class int | builtin-class type | 36 | runtime | +| b_condition.py:36 | ControlFlowNode for isinstance | Builtin-function isinstance | builtin-class builtin_function_or_method | 36 | runtime | +| b_condition.py:36 | ControlFlowNode for isinstance() | bool False | builtin-class bool | 36 | runtime | +| b_condition.py:36 | ControlFlowNode for isinstance() | bool True | builtin-class bool | 36 | runtime | +| b_condition.py:36 | ControlFlowNode for x | int 1 | builtin-class int | 31 | runtime | +| b_condition.py:36 | ControlFlowNode for x | int 7 | builtin-class int | 33 | runtime | +| b_condition.py:37 | ControlFlowNode for x | int 1 | builtin-class int | 31 | runtime | +| b_condition.py:37 | ControlFlowNode for x | int 7 | builtin-class int | 33 | runtime | +| b_condition.py:41 | ControlFlowNode for Attribute | int 1 | builtin-class int | 41 | import | +| b_condition.py:41 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 41 | import | +| b_condition.py:42 | ControlFlowNode for Compare | bool False | builtin-class bool | 42 | import | +| b_condition.py:42 | ControlFlowNode for Compare | bool True | builtin-class bool | 42 | import | +| b_condition.py:42 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 42 | import | +| b_condition.py:43 | ControlFlowNode for Attribute | int 1 | builtin-class int | 41 | import | +| b_condition.py:50 | ControlFlowNode for FunctionExpr | Function g | builtin-class function | 50 | import | +| b_condition.py:50 | ControlFlowNode for g | Function g | builtin-class function | 50 | import | +| b_condition.py:55 | ControlFlowNode for FunctionExpr | Function loop | builtin-class function | 55 | import | +| b_condition.py:55 | ControlFlowNode for loop | Function loop | builtin-class function | 55 | import | +| b_condition.py:61 | ControlFlowNode for FunctionExpr | Function double_attr_check | builtin-class function | 61 | import | +| b_condition.py:61 | ControlFlowNode for double_attr_check | Function double_attr_check | builtin-class function | 61 | import | +| b_condition.py:62 | ControlFlowNode for Compare | bool False | builtin-class bool | 62 | runtime | +| b_condition.py:62 | ControlFlowNode for Compare | bool True | builtin-class bool | 62 | runtime | +| b_condition.py:62 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | 62 | runtime | +| b_condition.py:65 | ControlFlowNode for Compare | bool False | builtin-class bool | 65 | runtime | +| b_condition.py:65 | ControlFlowNode for Compare | bool True | builtin-class bool | 65 | runtime | +| b_condition.py:65 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 65 | runtime | +| b_condition.py:66 | ControlFlowNode for Compare | bool False | builtin-class bool | 66 | runtime | +| b_condition.py:66 | ControlFlowNode for Compare | bool True | builtin-class bool | 66 | runtime | +| b_condition.py:69 | ControlFlowNode for FunctionExpr | Function h | builtin-class function | 69 | import | +| b_condition.py:69 | ControlFlowNode for h | Function h | builtin-class function | 69 | import | +| b_condition.py:70 | ControlFlowNode for IfExp | bool True | builtin-class bool | 70 | runtime | +| b_condition.py:70 | ControlFlowNode for True | bool True | builtin-class bool | 70 | runtime | +| b_condition.py:70 | ControlFlowNode for b | bool True | builtin-class bool | 70 | runtime | +| b_condition.py:71 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 71 | runtime | +| b_condition.py:71 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 71 | runtime | +| b_condition.py:71 | ControlFlowNode for b | bool True | builtin-class bool | 70 | runtime | +| b_condition.py:72 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 72 | runtime | +| b_condition.py:72 | ControlFlowNode for b | int 7 | builtin-class int | 72 | runtime | +| b_condition.py:73 | ControlFlowNode for b | bool True | builtin-class bool | 70 | runtime | +| b_condition.py:73 | ControlFlowNode for b | int 7 | builtin-class int | 72 | runtime | +| b_condition.py:75 | ControlFlowNode for FunctionExpr | Function k | builtin-class function | 75 | import | +| b_condition.py:75 | ControlFlowNode for k | Function k | builtin-class function | 75 | import | +| b_condition.py:76 | ControlFlowNode for t | builtin-class type | builtin-class type | 76 | runtime | +| b_condition.py:76 | ControlFlowNode for type | builtin-class type | builtin-class type | 76 | runtime | +| b_condition.py:77 | ControlFlowNode for Compare | bool True | builtin-class bool | 77 | runtime | +| b_condition.py:77 | ControlFlowNode for object | builtin-class object | builtin-class type | 77 | runtime | +| b_condition.py:77 | ControlFlowNode for t | builtin-class type | builtin-class type | 76 | runtime | +| b_condition.py:78 | ControlFlowNode for object | builtin-class object | builtin-class type | 78 | runtime | +| b_condition.py:78 | ControlFlowNode for t | builtin-class object | builtin-class type | 78 | runtime | +| b_condition.py:79 | ControlFlowNode for t | builtin-class object | builtin-class type | 78 | runtime | +| b_condition.py:81 | ControlFlowNode for FunctionExpr | Function odasa6261 | builtin-class function | 81 | import | +| b_condition.py:81 | ControlFlowNode for True | bool True | builtin-class bool | 81 | import | +| b_condition.py:81 | ControlFlowNode for odasa6261 | Function odasa6261 | builtin-class function | 81 | import | +| b_condition.py:82 | ControlFlowNode for callable | Builtin-function callable | builtin-class builtin_function_or_method | 82 | runtime | +| b_condition.py:82 | ControlFlowNode for callable() | bool False | builtin-class bool | 82 | runtime | +| b_condition.py:82 | ControlFlowNode for callable() | bool True | builtin-class bool | 82 | runtime | +| b_condition.py:82 | ControlFlowNode for foo | bool True | builtin-class bool | 81 | runtime | +| b_condition.py:83 | ControlFlowNode for FunctionExpr | Function bar | builtin-class function | 83 | runtime | +| b_condition.py:83 | ControlFlowNode for bar | Function bar | builtin-class function | 83 | runtime | +| b_condition.py:87 | ControlFlowNode for FunctionExpr | Function split_bool1 | builtin-class function | 87 | import | +| b_condition.py:87 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 87 | import | +| b_condition.py:87 | ControlFlowNode for split_bool1 | Function split_bool1 | builtin-class function | 87 | import | +| b_condition.py:88 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 87 | runtime | +| b_condition.py:88 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 87 | runtime | +| b_condition.py:90 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 87 | runtime | +| b_condition.py:90 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 87 | runtime | +| b_condition.py:92 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 87 | runtime | +| b_condition.py:93 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 87 | runtime | +| b_condition.py:96 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 87 | runtime | +| b_condition.py:97 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 87 | runtime | +| b_condition.py:101 | ControlFlowNode for FunctionExpr | Function not_or_not | builtin-class function | 101 | import | +| b_condition.py:101 | ControlFlowNode for a | a | builtin-class tuple | 101 | runtime | +| b_condition.py:101 | ControlFlowNode for not_or_not | Function not_or_not | builtin-class function | 101 | import | +| b_condition.py:102 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 102 | runtime | +| b_condition.py:102 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 102 | runtime | +| b_condition.py:102 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 102 | runtime | +| b_condition.py:102 | ControlFlowNode for a | a | builtin-class tuple | 101 | runtime | +| b_condition.py:102 | ControlFlowNode for isinstance | Builtin-function isinstance | builtin-class builtin_function_or_method | 102 | runtime | +| b_condition.py:102 | ControlFlowNode for isinstance() | bool False | builtin-class bool | 102 | runtime | +| b_condition.py:102 | ControlFlowNode for isinstance() | bool True | builtin-class bool | 102 | runtime | +| b_condition.py:102 | ControlFlowNode for list | builtin-class list | builtin-class type | 102 | runtime | +| b_condition.py:102 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type | 102 | runtime | +| b_condition.py:103 | ControlFlowNode for TypeError | builtin-class TypeError | builtin-class type | 103 | runtime | +| b_condition.py:103 | ControlFlowNode for TypeError() | TypeError() | builtin-class TypeError | 103 | runtime | +| b_condition.py:104 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 104 | runtime | +| b_condition.py:104 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 104 | runtime | +| b_condition.py:104 | ControlFlowNode for a | a | builtin-class tuple | 101 | runtime | +| b_condition.py:105 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 105 | runtime | +| b_condition.py:105 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 105 | runtime | +| b_condition.py:105 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 105 | runtime | +| b_condition.py:105 | ControlFlowNode for a | a | builtin-class tuple | 101 | runtime | +| b_condition.py:106 | ControlFlowNode for Exception | builtin-class Exception | builtin-class type | 106 | runtime | +| b_condition.py:106 | ControlFlowNode for Exception() | Exception() | builtin-class Exception | 106 | runtime | +| b_condition.py:107 | ControlFlowNode for Str | 'Hello' | builtin-class str | 107 | runtime | +| e_temporal.py:2 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | 2 | import | +| e_temporal.py:2 | ControlFlowNode for sys | Module sys | builtin-class module | 2 | import | +| e_temporal.py:4 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 4 | import | +| e_temporal.py:4 | ControlFlowNode for f | Function f | builtin-class function | 4 | import | +| e_temporal.py:5 | ControlFlowNode for Attribute | list object | builtin-class list | 5 | code/e_temporal.py:12 from import | +| e_temporal.py:5 | ControlFlowNode for Attribute | list object | builtin-class list | 5 | runtime | +| e_temporal.py:5 | ControlFlowNode for Compare | bool False | builtin-class bool | 5 | code/e_temporal.py:12 from import | +| e_temporal.py:5 | ControlFlowNode for Compare | bool False | builtin-class bool | 5 | runtime | +| e_temporal.py:5 | ControlFlowNode for Compare | bool True | builtin-class bool | 5 | code/e_temporal.py:12 from import | +| e_temporal.py:5 | ControlFlowNode for Compare | bool True | builtin-class bool | 5 | runtime | +| e_temporal.py:5 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | 5 | code/e_temporal.py:12 from import | +| e_temporal.py:5 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | 5 | runtime | +| e_temporal.py:5 | ControlFlowNode for len | Builtin-function len | builtin-class builtin_function_or_method | 5 | code/e_temporal.py:12 from import | +| e_temporal.py:5 | ControlFlowNode for len | Builtin-function len | builtin-class builtin_function_or_method | 5 | runtime | +| e_temporal.py:5 | ControlFlowNode for len() | len() | builtin-class int | 5 | code/e_temporal.py:12 from import | +| e_temporal.py:5 | ControlFlowNode for len() | len() | builtin-class int | 5 | runtime | +| e_temporal.py:5 | ControlFlowNode for sys | Module sys | builtin-class module | 2 | code/e_temporal.py:12 from import | +| e_temporal.py:5 | ControlFlowNode for sys | Module sys | builtin-class module | 2 | runtime | +| e_temporal.py:7 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 7 | code/e_temporal.py:12 from import | +| e_temporal.py:7 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 7 | runtime | +| e_temporal.py:9 | ControlFlowNode for FunctionExpr | Function g | builtin-class function | 9 | import | +| e_temporal.py:9 | ControlFlowNode for g | Function g | builtin-class function | 9 | import | +| e_temporal.py:10 | ControlFlowNode for arg | int 1 | builtin-class int | 7 | code/e_temporal.py:12 from import | +| e_temporal.py:12 | ControlFlowNode for f | Function f | builtin-class function | 4 | import | +| e_temporal.py:12 | ControlFlowNode for f() | int 1 | builtin-class int | 7 | import | +| e_temporal.py:12 | ControlFlowNode for g | Function g | builtin-class function | 9 | import | +| e_temporal.py:12 | ControlFlowNode for g() | int 1 | builtin-class int | 7 | import | +| e_temporal.py:12 | ControlFlowNode for x | int 1 | builtin-class int | 7 | import | +| g_class_init.py:3 | ControlFlowNode for C | class C | builtin-class type | 3 | import | +| g_class_init.py:3 | ControlFlowNode for ClassExpr | class C | builtin-class type | 3 | import | +| g_class_init.py:3 | ControlFlowNode for object | builtin-class object | builtin-class type | 3 | import | +| g_class_init.py:5 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 5 | import | +| g_class_init.py:5 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 5 | import | +| g_class_init.py:6 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 9 | runtime | +| g_class_init.py:6 | ControlFlowNode for self | self | class C | 5 | runtime | +| g_class_init.py:7 | ControlFlowNode for Attribute | int 1 | builtin-class int | 7 | runtime | +| g_class_init.py:7 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 7 | runtime | +| g_class_init.py:7 | ControlFlowNode for self | self | class C | 5 | runtime | +| g_class_init.py:9 | ControlFlowNode for FunctionExpr | Function _init | builtin-class function | 9 | import | +| g_class_init.py:9 | ControlFlowNode for _init | Function _init | builtin-class function | 9 | import | +| g_class_init.py:10 | ControlFlowNode for Attribute | int 2 | builtin-class int | 10 | code/g_class_init.py:6 from runtime | +| g_class_init.py:10 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | 10 | code/g_class_init.py:6 from runtime | +| g_class_init.py:10 | ControlFlowNode for self | self | class C | 5 | code/g_class_init.py:6 from runtime | +| g_class_init.py:11 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 13 | code/g_class_init.py:6 from runtime | +| g_class_init.py:11 | ControlFlowNode for self | self | class C | 5 | code/g_class_init.py:6 from runtime | +| g_class_init.py:13 | ControlFlowNode for FunctionExpr | Function _init2 | builtin-class function | 13 | import | +| g_class_init.py:13 | ControlFlowNode for _init2 | Function _init2 | builtin-class function | 13 | import | +| g_class_init.py:14 | ControlFlowNode for Attribute | int 3 | builtin-class int | 14 | code/g_class_init.py:11 from code/g_class_init.py:6 from runtime | +| g_class_init.py:14 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | 14 | code/g_class_init.py:11 from code/g_class_init.py:6 from runtime | +| g_class_init.py:14 | ControlFlowNode for self | self | class C | 5 | code/g_class_init.py:11 from code/g_class_init.py:6 from runtime | +| g_class_init.py:16 | ControlFlowNode for FunctionExpr | Function method | builtin-class function | 16 | import | +| g_class_init.py:16 | ControlFlowNode for method | Function method | builtin-class function | 16 | import | +| g_class_init.py:17 | ControlFlowNode for Attribute | int 1 | builtin-class int | 7 | runtime | +| g_class_init.py:17 | ControlFlowNode for self | self | class C | 16 | runtime | +| g_class_init.py:18 | ControlFlowNode for Attribute | int 2 | builtin-class int | 10 | runtime | +| g_class_init.py:18 | ControlFlowNode for int | builtin-class int | builtin-class type | 18 | runtime | +| g_class_init.py:18 | ControlFlowNode for isinstance | Builtin-function isinstance | builtin-class builtin_function_or_method | 18 | runtime | +| g_class_init.py:18 | ControlFlowNode for isinstance() | bool True | builtin-class bool | 18 | runtime | +| g_class_init.py:18 | ControlFlowNode for self | self | class C | 16 | runtime | +| g_class_init.py:19 | ControlFlowNode for Attribute | int 2 | builtin-class int | 10 | runtime | +| g_class_init.py:19 | ControlFlowNode for self | self | class C | 16 | runtime | +| g_class_init.py:20 | ControlFlowNode for Attribute | int 3 | builtin-class int | 14 | runtime | +| g_class_init.py:20 | ControlFlowNode for self | self | class C | 16 | runtime | +| g_class_init.py:24 | ControlFlowNode for ClassExpr | class Oddities | builtin-class type | 24 | import | +| g_class_init.py:24 | ControlFlowNode for Oddities | class Oddities | builtin-class type | 24 | import | +| g_class_init.py:24 | ControlFlowNode for object | builtin-class object | builtin-class type | 24 | import | +| g_class_init.py:26 | ControlFlowNode for int | builtin-class int | builtin-class type | 26 | import | +| g_class_init.py:27 | ControlFlowNode for float | builtin-class float | builtin-class type | 27 | import | +| g_class_init.py:28 | ControlFlowNode for l | Builtin-function len | builtin-class builtin_function_or_method | 28 | import | +| g_class_init.py:28 | ControlFlowNode for len | Builtin-function len | builtin-class builtin_function_or_method | 28 | import | +| g_class_init.py:29 | ControlFlowNode for h | Builtin-function hash | builtin-class builtin_function_or_method | 29 | import | +| g_class_init.py:29 | ControlFlowNode for hash | Builtin-function hash | builtin-class builtin_function_or_method | 29 | import | +| g_class_init.py:32 | ControlFlowNode for ClassExpr | class D | builtin-class type | 32 | import | +| g_class_init.py:32 | ControlFlowNode for D | class D | builtin-class type | 32 | import | +| g_class_init.py:32 | ControlFlowNode for object | builtin-class object | builtin-class type | 32 | import | +| g_class_init.py:34 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 34 | import | +| g_class_init.py:34 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 34 | import | +| g_class_init.py:35 | ControlFlowNode for Attribute | super().x | builtin-class method | 35 | runtime | +| g_class_init.py:35 | ControlFlowNode for D | class D | builtin-class type | 32 | runtime | +| g_class_init.py:35 | ControlFlowNode for self | self | class D | 34 | runtime | +| g_class_init.py:35 | ControlFlowNode for super | builtin-class super | builtin-class type | 35 | runtime | +| g_class_init.py:35 | ControlFlowNode for super() | super() | builtin-class super | 35 | runtime | +| g_class_init.py:36 | ControlFlowNode for Attribute | super().__init__ | builtin-class method | 36 | runtime | +| g_class_init.py:36 | ControlFlowNode for D | class D | builtin-class type | 32 | runtime | +| g_class_init.py:36 | ControlFlowNode for self | self | class D | 34 | runtime | +| g_class_init.py:36 | ControlFlowNode for super | builtin-class super | builtin-class type | 36 | runtime | +| g_class_init.py:36 | ControlFlowNode for super() | super() | builtin-class super | 36 | runtime | +| g_class_init.py:42 | ControlFlowNode for Str | 'v2' | builtin-class str | 42 | import | +| g_class_init.py:42 | ControlFlowNode for V2 | 'v2' | builtin-class str | 42 | import | +| g_class_init.py:43 | ControlFlowNode for Str | 'v3' | builtin-class str | 43 | import | +| g_class_init.py:43 | ControlFlowNode for V3 | 'v3' | builtin-class str | 43 | import | +| g_class_init.py:45 | ControlFlowNode for ClassExpr | class E | builtin-class type | 45 | import | +| g_class_init.py:45 | ControlFlowNode for E | class E | builtin-class type | 45 | import | +| g_class_init.py:45 | ControlFlowNode for object | builtin-class object | builtin-class type | 45 | import | +| g_class_init.py:46 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 46 | import | +| g_class_init.py:46 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 46 | import | +| g_class_init.py:48 | ControlFlowNode for Attribute | 'v2' | builtin-class str | 42 | runtime | +| g_class_init.py:48 | ControlFlowNode for V2 | 'v2' | builtin-class str | 42 | runtime | +| g_class_init.py:48 | ControlFlowNode for self | self | class E | 46 | runtime | +| g_class_init.py:50 | ControlFlowNode for Attribute | 'v3' | builtin-class str | 43 | runtime | +| g_class_init.py:50 | ControlFlowNode for V3 | 'v3' | builtin-class str | 43 | runtime | +| g_class_init.py:50 | ControlFlowNode for self | self | class E | 46 | runtime | +| g_class_init.py:52 | ControlFlowNode for FunctionExpr | Function meth | builtin-class function | 52 | import | +| g_class_init.py:52 | ControlFlowNode for meth | Function meth | builtin-class function | 52 | import | +| g_class_init.py:53 | ControlFlowNode for Attribute | 'v2' | builtin-class str | 42 | runtime | +| g_class_init.py:53 | ControlFlowNode for Attribute | 'v3' | builtin-class str | 43 | runtime | +| g_class_init.py:53 | ControlFlowNode for Compare | bool False | builtin-class bool | 53 | runtime | +| g_class_init.py:53 | ControlFlowNode for Compare | bool True | builtin-class bool | 53 | runtime | +| g_class_init.py:53 | ControlFlowNode for V2 | 'v2' | builtin-class str | 42 | runtime | +| g_class_init.py:53 | ControlFlowNode for self | self | class E | 52 | runtime | +| h_classes.py:1 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | 1 | import | +| h_classes.py:1 | ControlFlowNode for sys | Module sys | builtin-class module | 1 | import | +| h_classes.py:3 | ControlFlowNode for C | class C | builtin-class type | 3 | import | +| h_classes.py:3 | ControlFlowNode for ClassExpr | class C | builtin-class type | 3 | import | +| h_classes.py:3 | ControlFlowNode for object | builtin-class object | builtin-class type | 3 | import | +| h_classes.py:5 | ControlFlowNode for Str | 'C_x' | builtin-class str | 5 | import | +| h_classes.py:5 | ControlFlowNode for x | 'C_x' | builtin-class str | 5 | import | +| h_classes.py:7 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 7 | import | +| h_classes.py:7 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 7 | import | +| h_classes.py:8 | ControlFlowNode for Attribute | 'c_y' | builtin-class str | 8 | code/h_classes.py:10 from import | +| h_classes.py:8 | ControlFlowNode for Attribute | 'c_y' | builtin-class str | 8 | code/h_classes.py:15 from runtime | +| h_classes.py:8 | ControlFlowNode for Attribute | 'c_y' | builtin-class str | 8 | runtime | +| h_classes.py:8 | ControlFlowNode for Str | 'c_y' | builtin-class str | 8 | code/h_classes.py:10 from import | +| h_classes.py:8 | ControlFlowNode for Str | 'c_y' | builtin-class str | 8 | code/h_classes.py:15 from runtime | +| h_classes.py:8 | ControlFlowNode for Str | 'c_y' | builtin-class str | 8 | runtime | +| h_classes.py:8 | ControlFlowNode for self | self | class C | 7 | runtime | +| h_classes.py:10 | ControlFlowNode for C | class C | builtin-class type | 3 | import | +| h_classes.py:10 | ControlFlowNode for C() | C() | class C | 10 | import | +| h_classes.py:10 | ControlFlowNode for type | builtin-class type | builtin-class type | 10 | import | +| h_classes.py:10 | ControlFlowNode for type() | class C | builtin-class type | 3 | import | +| h_classes.py:11 | ControlFlowNode for sys | Module sys | builtin-class module | 1 | import | +| h_classes.py:11 | ControlFlowNode for type | builtin-class type | builtin-class type | 11 | import | +| h_classes.py:11 | ControlFlowNode for type() | builtin-class module | builtin-class type | 11 | import | +| h_classes.py:12 | ControlFlowNode for Dict | Dict | builtin-class dict | 12 | import | +| h_classes.py:12 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 12 | import | +| h_classes.py:12 | ControlFlowNode for object | builtin-class object | builtin-class type | 12 | import | +| h_classes.py:12 | ControlFlowNode for type | builtin-class type | builtin-class type | 12 | import | +| h_classes.py:14 | ControlFlowNode for FunctionExpr | Function k | builtin-class function | 14 | import | +| h_classes.py:14 | ControlFlowNode for k | Function k | builtin-class function | 14 | import | +| h_classes.py:15 | ControlFlowNode for C | class C | builtin-class type | 3 | runtime | +| h_classes.py:15 | ControlFlowNode for C() | C() | class C | 15 | runtime | +| h_classes.py:15 | ControlFlowNode for type | builtin-class type | builtin-class type | 15 | runtime | +| h_classes.py:15 | ControlFlowNode for type() | class C | builtin-class type | 3 | runtime | +| h_classes.py:16 | ControlFlowNode for sys | Module sys | builtin-class module | 1 | runtime | +| h_classes.py:16 | ControlFlowNode for type | builtin-class type | builtin-class type | 16 | runtime | +| h_classes.py:16 | ControlFlowNode for type() | builtin-class module | builtin-class type | 16 | runtime | +| h_classes.py:17 | ControlFlowNode for type | builtin-class type | builtin-class type | 17 | runtime | +| h_classes.py:17 | ControlFlowNode for type() | *UNKNOWN TYPE* | builtin-class type | 17 | runtime | +| h_classes.py:18 | ControlFlowNode for Dict | Dict | builtin-class dict | 18 | runtime | +| h_classes.py:18 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 18 | runtime | +| h_classes.py:18 | ControlFlowNode for object | builtin-class object | builtin-class type | 18 | runtime | +| h_classes.py:18 | ControlFlowNode for type | builtin-class type | builtin-class type | 18 | runtime | +| h_classes.py:23 | ControlFlowNode for Base | class Base | builtin-class type | 23 | import | +| h_classes.py:23 | ControlFlowNode for ClassExpr | class Base | builtin-class type | 23 | import | +| h_classes.py:23 | ControlFlowNode for object | builtin-class object | builtin-class type | 23 | import | +| h_classes.py:25 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 25 | import | +| h_classes.py:25 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 25 | import | +| h_classes.py:26 | ControlFlowNode for Compare | bool False | builtin-class bool | 26 | runtime | +| h_classes.py:26 | ControlFlowNode for Compare | bool True | builtin-class bool | 26 | runtime | +| h_classes.py:26 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 26 | runtime | +| h_classes.py:27 | ControlFlowNode for Attribute | class Derived1 | builtin-class type | 33 | runtime | +| h_classes.py:27 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | 33 | runtime | +| h_classes.py:27 | ControlFlowNode for self | self | class Base | 25 | runtime | +| h_classes.py:28 | ControlFlowNode for Compare | bool False | builtin-class bool | 28 | runtime | +| h_classes.py:28 | ControlFlowNode for Compare | bool True | builtin-class bool | 28 | runtime | +| h_classes.py:28 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | 28 | runtime | +| h_classes.py:29 | ControlFlowNode for Attribute | class Derived2 | builtin-class type | 36 | runtime | +| h_classes.py:29 | ControlFlowNode for Derived2 | class Derived2 | builtin-class type | 36 | runtime | +| h_classes.py:29 | ControlFlowNode for self | self | class Base | 25 | runtime | +| h_classes.py:31 | ControlFlowNode for Attribute | class Derived3 | builtin-class type | 39 | runtime | +| h_classes.py:31 | ControlFlowNode for Derived3 | class Derived3 | builtin-class type | 39 | runtime | +| h_classes.py:31 | ControlFlowNode for self | self | class Base | 25 | runtime | +| h_classes.py:33 | ControlFlowNode for Base | class Base | builtin-class type | 23 | import | +| h_classes.py:33 | ControlFlowNode for ClassExpr | class Derived1 | builtin-class type | 33 | import | +| h_classes.py:33 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | 33 | import | +| h_classes.py:36 | ControlFlowNode for Base | class Base | builtin-class type | 23 | import | +| h_classes.py:36 | ControlFlowNode for ClassExpr | class Derived2 | builtin-class type | 36 | import | +| h_classes.py:36 | ControlFlowNode for Derived2 | class Derived2 | builtin-class type | 36 | import | +| h_classes.py:39 | ControlFlowNode for Base | class Base | builtin-class type | 23 | import | +| h_classes.py:39 | ControlFlowNode for ClassExpr | class Derived3 | builtin-class type | 39 | import | +| h_classes.py:39 | ControlFlowNode for Derived3 | class Derived3 | builtin-class type | 39 | import | +| h_classes.py:42 | ControlFlowNode for Base | class Base | builtin-class type | 23 | import | +| h_classes.py:45 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 45 | import | +| h_classes.py:45 | ControlFlowNode for f | Function f | builtin-class function | 45 | import | +| h_classes.py:48 | ControlFlowNode for ClassExpr | class D | builtin-class type | 48 | import | +| h_classes.py:48 | ControlFlowNode for D | class D | builtin-class type | 48 | import | +| h_classes.py:48 | ControlFlowNode for object | builtin-class object | builtin-class type | 48 | import | +| h_classes.py:50 | ControlFlowNode for f | Function f | builtin-class function | 45 | import | +| h_classes.py:50 | ControlFlowNode for m | Function f | builtin-class function | 45 | import | +| h_classes.py:52 | ControlFlowNode for FunctionExpr | Function n | builtin-class function | 52 | import | +| h_classes.py:52 | ControlFlowNode for n | Function n | builtin-class function | 52 | import | +| i_imports.py:3 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 3 | import | +| i_imports.py:3 | ControlFlowNode for a | int 1 | builtin-class int | 3 | import | +| i_imports.py:4 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | 4 | import | +| i_imports.py:4 | ControlFlowNode for b | int 2 | builtin-class int | 4 | import | +| i_imports.py:5 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | 5 | import | +| i_imports.py:5 | ControlFlowNode for c | int 3 | builtin-class int | 5 | import | +| i_imports.py:7 | ControlFlowNode for ImportExpr | Module code.xyz | builtin-class module | 7 | import | +| i_imports.py:8 | ControlFlowNode for ImportExpr | Module code | builtin-class module | 8 | import | +| i_imports.py:8 | ControlFlowNode for ImportMember | Module code.xyz | builtin-class module | 0 | import | +| i_imports.py:8 | ControlFlowNode for xyz | Module code.xyz | builtin-class module | 0 | import | +| i_imports.py:9 | ControlFlowNode for Attribute | float 1.0 | builtin-class float | 2 | import | +| i_imports.py:9 | ControlFlowNode for xyz | Module code.xyz | builtin-class module | 0 | import | +| i_imports.py:10 | ControlFlowNode for z | float 3.0 | builtin-class float | 4 | import | +| i_imports.py:11 | ControlFlowNode for a | int 1 | builtin-class int | 3 | import | +| i_imports.py:13 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | 13 | import | +| i_imports.py:13 | ControlFlowNode for ImportMember | list object | builtin-class list | 13 | import | +| i_imports.py:13 | ControlFlowNode for argv | list object | builtin-class list | 13 | import | +| i_imports.py:15 | ControlFlowNode for argv | list object | builtin-class list | 13 | import | +| i_imports.py:17 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | 17 | import | +| i_imports.py:17 | ControlFlowNode for sys | Module sys | builtin-class module | 17 | import | +| i_imports.py:18 | ControlFlowNode for Attribute | list object | builtin-class list | 18 | import | +| i_imports.py:18 | ControlFlowNode for sys | Module sys | builtin-class module | 17 | import | +| i_imports.py:23 | ControlFlowNode for ImportExpr | Module code | builtin-class module | 23 | import | +| i_imports.py:23 | ControlFlowNode for code | Module code | builtin-class module | 23 | import | +| i_imports.py:24 | ControlFlowNode for Attribute | Module code.package.x | builtin-class module | 0 | import | +| i_imports.py:24 | ControlFlowNode for code | Module code | builtin-class module | 23 | import | +| i_imports.py:27 | ControlFlowNode for ImportExpr | Module code.test_package | builtin-class module | 27 | import | +| i_imports.py:29 | ControlFlowNode for ImportExpr | Module _io | builtin-class module | 29 | import | +| i_imports.py:29 | ControlFlowNode for _io | Module _io | builtin-class module | 29 | import | +| i_imports.py:30 | ControlFlowNode for Attribute | builtin-class _io.StringIO | builtin-class type | 30 | import | +| i_imports.py:30 | ControlFlowNode for StringIO | builtin-class _io.StringIO | builtin-class type | 30 | import | +| i_imports.py:30 | ControlFlowNode for _io | Module _io | builtin-class module | 29 | import | +| i_imports.py:31 | ControlFlowNode for Attribute | builtin-class _io.BytesIO | builtin-class type | 31 | import | +| i_imports.py:31 | ControlFlowNode for BytesIO | builtin-class _io.BytesIO | builtin-class type | 31 | import | +| i_imports.py:31 | ControlFlowNode for _io | Module _io | builtin-class module | 29 | import | +| i_imports.py:33 | ControlFlowNode for ImportExpr | Module io | builtin-class module | 33 | import | +| i_imports.py:33 | ControlFlowNode for io | Module io | builtin-class module | 33 | import | +| i_imports.py:34 | ControlFlowNode for Attribute | builtin-class _io.StringIO | builtin-class type | 55 | import | +| i_imports.py:34 | ControlFlowNode for StringIO | builtin-class _io.StringIO | builtin-class type | 55 | import | +| i_imports.py:34 | ControlFlowNode for io | Module io | builtin-class module | 33 | import | +| i_imports.py:35 | ControlFlowNode for Attribute | builtin-class _io.BytesIO | builtin-class type | 55 | import | +| i_imports.py:35 | ControlFlowNode for BytesIO | builtin-class _io.BytesIO | builtin-class type | 55 | import | +| i_imports.py:35 | ControlFlowNode for io | Module io | builtin-class module | 33 | import | +| i_imports.py:37 | ControlFlowNode for ImportExpr | Module code | builtin-class module | 37 | import | +| i_imports.py:37 | ControlFlowNode for code | Module code | builtin-class module | 37 | import | +| i_imports.py:38 | ControlFlowNode for Attribute | Function f2 | builtin-class function | 24 | import | +| i_imports.py:38 | ControlFlowNode for Attribute | Module code.n_nesting | builtin-class module | 0 | import | +| i_imports.py:38 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 24 | import | +| i_imports.py:38 | ControlFlowNode for code | Module code | builtin-class module | 37 | import | +| j_convoluted_imports.py:2 | ControlFlowNode for ImportExpr | Module code.package | builtin-class module | 2 | import | +| j_convoluted_imports.py:3 | ControlFlowNode for ImportMember | Function module | builtin-class function | 2 | import | +| j_convoluted_imports.py:3 | ControlFlowNode for module | Function module | builtin-class function | 2 | import | +| j_convoluted_imports.py:5 | ControlFlowNode for ImportExpr | Module code.package | builtin-class module | 5 | import | +| j_convoluted_imports.py:6 | ControlFlowNode for ImportMember | Module code.package.x | builtin-class module | 0 | import | +| j_convoluted_imports.py:6 | ControlFlowNode for x | Module code.package.x | builtin-class module | 0 | import | +| j_convoluted_imports.py:9 | ControlFlowNode for C | class C | builtin-class type | 9 | import | +| j_convoluted_imports.py:9 | ControlFlowNode for ClassExpr | class C | builtin-class type | 9 | import | +| j_convoluted_imports.py:9 | ControlFlowNode for object | builtin-class object | builtin-class type | 9 | import | +| j_convoluted_imports.py:11 | ControlFlowNode for ImportExpr | Module code.package | builtin-class module | 11 | import | +| j_convoluted_imports.py:11 | ControlFlowNode for ImportMember | int 7 | builtin-class int | 5 | import | +| j_convoluted_imports.py:11 | ControlFlowNode for module2 | int 7 | builtin-class int | 5 | import | +| j_convoluted_imports.py:13 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 13 | import | +| j_convoluted_imports.py:13 | ControlFlowNode for f | Function f | builtin-class function | 13 | import | +| j_convoluted_imports.py:14 | ControlFlowNode for ImportExpr | Module code.package | builtin-class module | 14 | runtime | +| j_convoluted_imports.py:14 | ControlFlowNode for ImportMember | Module code.package.x | builtin-class module | 0 | runtime | +| j_convoluted_imports.py:14 | ControlFlowNode for x | Module code.package.x | builtin-class module | 0 | runtime | +| j_convoluted_imports.py:16 | ControlFlowNode for ImportExpr | Module code.package | builtin-class module | 16 | import | +| j_convoluted_imports.py:16 | ControlFlowNode for ImportMember | Module code.package.moduleX | builtin-class module | 0 | import | +| j_convoluted_imports.py:16 | ControlFlowNode for moduleX | Module code.package.moduleX | builtin-class module | 0 | import | +| j_convoluted_imports.py:17 | ControlFlowNode for Attribute | class Y | builtin-class type | 1 | import | +| j_convoluted_imports.py:17 | ControlFlowNode for moduleX | Module code.package.moduleX | builtin-class module | 0 | import | +| k_getsetattr.py:4 | ControlFlowNode for C | class C | builtin-class type | 4 | import | +| k_getsetattr.py:4 | ControlFlowNode for ClassExpr | class C | builtin-class type | 4 | import | +| k_getsetattr.py:4 | ControlFlowNode for object | builtin-class object | builtin-class type | 4 | import | +| k_getsetattr.py:6 | ControlFlowNode for FunctionExpr | Function meth1 | builtin-class function | 6 | import | +| k_getsetattr.py:6 | ControlFlowNode for meth1 | Function meth1 | builtin-class function | 6 | import | +| k_getsetattr.py:7 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 7 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:7 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 7 | runtime | +| k_getsetattr.py:7 | ControlFlowNode for Str | 'a' | builtin-class str | 7 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:7 | ControlFlowNode for Str | 'a' | builtin-class str | 7 | runtime | +| k_getsetattr.py:7 | ControlFlowNode for self | self | class C | 6 | runtime | +| k_getsetattr.py:7 | ControlFlowNode for self | self | class C | 12 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:7 | ControlFlowNode for setattr | Builtin-function setattr | builtin-class builtin_function_or_method | 7 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:7 | ControlFlowNode for setattr | Builtin-function setattr | builtin-class builtin_function_or_method | 7 | runtime | +| k_getsetattr.py:7 | ControlFlowNode for setattr() | NoneType None | builtin-class NoneType | 7 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:7 | ControlFlowNode for setattr() | NoneType None | builtin-class NoneType | 7 | runtime | +| k_getsetattr.py:8 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 8 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:8 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 8 | runtime | +| k_getsetattr.py:8 | ControlFlowNode for Str | 'b' | builtin-class str | 8 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:8 | ControlFlowNode for Str | 'b' | builtin-class str | 8 | runtime | +| k_getsetattr.py:8 | ControlFlowNode for self | self | class C | 6 | runtime | +| k_getsetattr.py:8 | ControlFlowNode for self | self | class C | 12 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:8 | ControlFlowNode for setattr | Builtin-function setattr | builtin-class builtin_function_or_method | 8 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:8 | ControlFlowNode for setattr | Builtin-function setattr | builtin-class builtin_function_or_method | 8 | runtime | +| k_getsetattr.py:8 | ControlFlowNode for setattr() | NoneType None | builtin-class NoneType | 8 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:8 | ControlFlowNode for setattr() | NoneType None | builtin-class NoneType | 8 | runtime | +| k_getsetattr.py:9 | ControlFlowNode for Str | 'a' | builtin-class str | 9 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:9 | ControlFlowNode for Str | 'a' | builtin-class str | 9 | runtime | +| k_getsetattr.py:9 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 9 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:9 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 9 | runtime | +| k_getsetattr.py:9 | ControlFlowNode for getattr() | int 0 | builtin-class int | 7 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:9 | ControlFlowNode for getattr() | int 0 | builtin-class int | 7 | runtime | +| k_getsetattr.py:9 | ControlFlowNode for self | self | class C | 6 | runtime | +| k_getsetattr.py:9 | ControlFlowNode for self | self | class C | 12 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:10 | ControlFlowNode for Str | 'c' | builtin-class str | 10 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:10 | ControlFlowNode for Str | 'c' | builtin-class str | 10 | runtime | +| k_getsetattr.py:10 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 10 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:10 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 10 | runtime | +| k_getsetattr.py:10 | ControlFlowNode for getattr() | int 2 | builtin-class int | 14 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:10 | ControlFlowNode for self | self | class C | 6 | runtime | +| k_getsetattr.py:10 | ControlFlowNode for self | self | class C | 12 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:12 | ControlFlowNode for FunctionExpr | Function meth2 | builtin-class function | 12 | import | +| k_getsetattr.py:12 | ControlFlowNode for meth2 | Function meth2 | builtin-class function | 12 | import | +| k_getsetattr.py:13 | ControlFlowNode for FloatLiteral | float 7.0 | builtin-class float | 13 | runtime | +| k_getsetattr.py:13 | ControlFlowNode for Str | 'a' | builtin-class str | 13 | runtime | +| k_getsetattr.py:13 | ControlFlowNode for self | self | class C | 12 | runtime | +| k_getsetattr.py:13 | ControlFlowNode for setattr | Builtin-function setattr | builtin-class builtin_function_or_method | 13 | runtime | +| k_getsetattr.py:13 | ControlFlowNode for setattr() | NoneType None | builtin-class NoneType | 13 | runtime | +| k_getsetattr.py:14 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | 14 | runtime | +| k_getsetattr.py:14 | ControlFlowNode for Str | 'c' | builtin-class str | 14 | runtime | +| k_getsetattr.py:14 | ControlFlowNode for self | self | class C | 12 | runtime | +| k_getsetattr.py:14 | ControlFlowNode for setattr | Builtin-function setattr | builtin-class builtin_function_or_method | 14 | runtime | +| k_getsetattr.py:14 | ControlFlowNode for setattr() | NoneType None | builtin-class NoneType | 14 | runtime | +| k_getsetattr.py:15 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 6 | runtime | +| k_getsetattr.py:15 | ControlFlowNode for self | self | class C | 12 | runtime | +| k_getsetattr.py:16 | ControlFlowNode for Str | 'a' | builtin-class str | 16 | runtime | +| k_getsetattr.py:16 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 16 | runtime | +| k_getsetattr.py:16 | ControlFlowNode for getattr() | int 0 | builtin-class int | 7 | runtime | +| k_getsetattr.py:16 | ControlFlowNode for self | self | class C | 12 | runtime | +| k_getsetattr.py:17 | ControlFlowNode for Str | 'b' | builtin-class str | 17 | runtime | +| k_getsetattr.py:17 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 17 | runtime | +| k_getsetattr.py:17 | ControlFlowNode for getattr() | int 1 | builtin-class int | 8 | runtime | +| k_getsetattr.py:17 | ControlFlowNode for self | self | class C | 12 | runtime | +| k_getsetattr.py:18 | ControlFlowNode for Str | 'c' | builtin-class str | 18 | runtime | +| k_getsetattr.py:18 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 18 | runtime | +| k_getsetattr.py:18 | ControlFlowNode for getattr() | int 2 | builtin-class int | 14 | runtime | +| k_getsetattr.py:18 | ControlFlowNode for self | self | class C | 12 | runtime | +| k_getsetattr.py:21 | ControlFlowNode for FunctionExpr | Function k | builtin-class function | 21 | import | +| k_getsetattr.py:21 | ControlFlowNode for k | Function k | builtin-class function | 21 | import | +| k_getsetattr.py:22 | ControlFlowNode for C | class C | builtin-class type | 4 | runtime | +| k_getsetattr.py:22 | ControlFlowNode for C() | C() | class C | 22 | runtime | +| k_getsetattr.py:22 | ControlFlowNode for c1 | C() | class C | 22 | runtime | +| k_getsetattr.py:23 | ControlFlowNode for C | class C | builtin-class type | 4 | runtime | +| k_getsetattr.py:23 | ControlFlowNode for C() | C() | class C | 23 | runtime | +| k_getsetattr.py:23 | ControlFlowNode for c2 | C() | class C | 23 | runtime | +| k_getsetattr.py:24 | ControlFlowNode for C | class C | builtin-class type | 4 | runtime | +| k_getsetattr.py:24 | ControlFlowNode for C() | C() | class C | 24 | runtime | +| k_getsetattr.py:24 | ControlFlowNode for c3 | C() | class C | 24 | runtime | +| k_getsetattr.py:25 | ControlFlowNode for Attribute | int 10 | builtin-class int | 25 | runtime | +| k_getsetattr.py:25 | ControlFlowNode for IntegerLiteral | int 10 | builtin-class int | 25 | runtime | +| k_getsetattr.py:25 | ControlFlowNode for c1 | C() | class C | 22 | runtime | +| k_getsetattr.py:27 | ControlFlowNode for Attribute | int 20 | builtin-class int | 27 | runtime | +| k_getsetattr.py:27 | ControlFlowNode for IntegerLiteral | int 20 | builtin-class int | 27 | runtime | +| k_getsetattr.py:27 | ControlFlowNode for c2 | C() | class C | 23 | runtime | +| k_getsetattr.py:28 | ControlFlowNode for Attribute | int 10 | builtin-class int | 25 | runtime | +| k_getsetattr.py:28 | ControlFlowNode for c1 | C() | class C | 22 | runtime | +| k_getsetattr.py:29 | ControlFlowNode for Attribute | int 20 | builtin-class int | 27 | runtime | +| k_getsetattr.py:29 | ControlFlowNode for c2 | C() | class C | 23 | runtime | +| k_getsetattr.py:30 | ControlFlowNode for c3 | C() | class C | 24 | runtime | +| k_getsetattr.py:31 | ControlFlowNode for Attribute | int 30 | builtin-class int | 31 | runtime | +| k_getsetattr.py:31 | ControlFlowNode for IntegerLiteral | int 30 | builtin-class int | 31 | runtime | +| k_getsetattr.py:31 | ControlFlowNode for c3 | C() | class C | 24 | runtime | +| l_calls.py:3 | ControlFlowNode for FunctionExpr | Function foo | builtin-class function | 3 | import | +| l_calls.py:3 | ControlFlowNode for List | List | builtin-class list | 3 | import | +| l_calls.py:3 | ControlFlowNode for foo | Function foo | builtin-class function | 3 | import | +| l_calls.py:4 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 4 | code/l_calls.py:9 from import | +| l_calls.py:4 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 4 | runtime | +| l_calls.py:4 | ControlFlowNode for Str | 'x' | builtin-class str | 4 | code/l_calls.py:9 from import | +| l_calls.py:4 | ControlFlowNode for Str | 'x' | builtin-class str | 4 | runtime | +| l_calls.py:4 | ControlFlowNode for x | List | builtin-class list | 3 | code/l_calls.py:9 from import | +| l_calls.py:4 | ControlFlowNode for x | List | builtin-class list | 3 | runtime | +| l_calls.py:6 | ControlFlowNode for FunctionExpr | Function bar | builtin-class function | 6 | import | +| l_calls.py:6 | ControlFlowNode for List | List | builtin-class list | 6 | import | +| l_calls.py:6 | ControlFlowNode for bar | Function bar | builtin-class function | 6 | import | +| l_calls.py:7 | ControlFlowNode for len | Builtin-function len | builtin-class builtin_function_or_method | 7 | code/l_calls.py:10 from import | +| l_calls.py:7 | ControlFlowNode for len | Builtin-function len | builtin-class builtin_function_or_method | 7 | runtime | +| l_calls.py:7 | ControlFlowNode for len() | len() | builtin-class int | 7 | code/l_calls.py:10 from import | +| l_calls.py:7 | ControlFlowNode for len() | len() | builtin-class int | 7 | runtime | +| l_calls.py:7 | ControlFlowNode for x | List | builtin-class list | 6 | code/l_calls.py:10 from import | +| l_calls.py:7 | ControlFlowNode for x | List | builtin-class list | 6 | runtime | +| l_calls.py:9 | ControlFlowNode for foo | Function foo | builtin-class function | 3 | import | +| l_calls.py:9 | ControlFlowNode for foo() | NoneType None | builtin-class NoneType | 4 | import | +| l_calls.py:10 | ControlFlowNode for bar | Function bar | builtin-class function | 6 | import | +| l_calls.py:10 | ControlFlowNode for bar() | len() | builtin-class int | 7 | import | +| l_calls.py:12 | ControlFlowNode for ClassExpr | class Owner | builtin-class type | 12 | import | +| l_calls.py:12 | ControlFlowNode for Owner | class Owner | builtin-class type | 12 | import | +| l_calls.py:12 | ControlFlowNode for object | builtin-class object | builtin-class type | 12 | import | +| l_calls.py:14 | ControlFlowNode for classmethod | builtin-class classmethod | builtin-class type | 14 | import | +| l_calls.py:14 | ControlFlowNode for classmethod() | classmethod() | builtin-class classmethod | 14 | import | +| l_calls.py:15 | ControlFlowNode for FunctionExpr | Function cm | builtin-class function | 15 | import | +| l_calls.py:15 | ControlFlowNode for cm | classmethod() | builtin-class classmethod | 14 | import | +| l_calls.py:16 | ControlFlowNode for cls | class Owner | builtin-class type | 23 | code/l_calls.py:24 from runtime | +| l_calls.py:18 | ControlFlowNode for classmethod | builtin-class classmethod | builtin-class type | 18 | import | +| l_calls.py:18 | ControlFlowNode for classmethod() | classmethod() | builtin-class classmethod | 18 | import | +| l_calls.py:19 | ControlFlowNode for FunctionExpr | Function cm2 | builtin-class function | 19 | import | +| l_calls.py:19 | ControlFlowNode for cm2 | classmethod() | builtin-class classmethod | 18 | import | +| l_calls.py:20 | ControlFlowNode for arg | int 1 | builtin-class int | 25 | code/l_calls.py:25 from runtime | +| l_calls.py:23 | ControlFlowNode for FunctionExpr | Function m | builtin-class function | 23 | import | +| l_calls.py:23 | ControlFlowNode for m | Function m | builtin-class function | 23 | import | +| l_calls.py:24 | ControlFlowNode for Attribute() | class Owner | builtin-class type | 23 | runtime | +| l_calls.py:24 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 24 | runtime | +| l_calls.py:24 | ControlFlowNode for a | class Owner | builtin-class type | 23 | runtime | +| l_calls.py:24 | ControlFlowNode for self | self | class Owner | 23 | runtime | +| l_calls.py:25 | ControlFlowNode for Attribute() | int 1 | builtin-class int | 25 | runtime | +| l_calls.py:25 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 25 | runtime | +| l_calls.py:25 | ControlFlowNode for a | class Owner | builtin-class type | 23 | runtime | +| m_attributes.py:3 | ControlFlowNode for C | class C | builtin-class type | 3 | import | +| m_attributes.py:3 | ControlFlowNode for ClassExpr | class C | builtin-class type | 3 | import | +| m_attributes.py:3 | ControlFlowNode for object | builtin-class object | builtin-class type | 3 | import | +| m_attributes.py:5 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 5 | import | +| m_attributes.py:5 | ControlFlowNode for IntegerLiteral | int 17 | builtin-class int | 5 | import | +| m_attributes.py:5 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 5 | import | +| m_attributes.py:6 | ControlFlowNode for Attribute | int 17 | builtin-class int | 5 | runtime | +| m_attributes.py:6 | ControlFlowNode for Attribute | int 100 | builtin-class int | 13 | code/m_attributes.py:13 from import | +| m_attributes.py:6 | ControlFlowNode for a | int 17 | builtin-class int | 5 | runtime | +| m_attributes.py:6 | ControlFlowNode for a | int 100 | builtin-class int | 13 | code/m_attributes.py:13 from import | +| m_attributes.py:6 | ControlFlowNode for self | self | class C | 5 | runtime | +| m_attributes.py:8 | ControlFlowNode for FunctionExpr | Function foo | builtin-class function | 8 | import | +| m_attributes.py:8 | ControlFlowNode for foo | Function foo | builtin-class function | 8 | import | +| m_attributes.py:9 | ControlFlowNode for Attribute | int 17 | builtin-class int | 5 | runtime | +| m_attributes.py:9 | ControlFlowNode for self | self | class C | 8 | runtime | +| m_attributes.py:10 | ControlFlowNode for other | C() | class C | 12 | code/m_attributes.py:12 from import | +| m_attributes.py:10 | ControlFlowNode for other | C() | class C | 13 | code/m_attributes.py:13 from import | +| m_attributes.py:12 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 8 | import | +| m_attributes.py:12 | ControlFlowNode for C | class C | builtin-class type | 3 | import | +| m_attributes.py:12 | ControlFlowNode for C() | C() | class C | 12 | import | +| m_attributes.py:13 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 8 | import | +| m_attributes.py:13 | ControlFlowNode for C | class C | builtin-class type | 3 | import | +| m_attributes.py:13 | ControlFlowNode for C() | C() | class C | 13 | import | +| m_attributes.py:13 | ControlFlowNode for IntegerLiteral | int 100 | builtin-class int | 13 | import | +| n_nesting.py:8 | ControlFlowNode for FunctionExpr | Function foo | builtin-class function | 8 | import | +| n_nesting.py:8 | ControlFlowNode for True | bool True | builtin-class bool | 8 | import | +| n_nesting.py:8 | ControlFlowNode for foo | Function foo | builtin-class function | 8 | import | +| n_nesting.py:9 | ControlFlowNode for callable | Builtin-function callable | builtin-class builtin_function_or_method | 9 | runtime | +| n_nesting.py:9 | ControlFlowNode for callable() | bool False | builtin-class bool | 9 | runtime | +| n_nesting.py:9 | ControlFlowNode for callable() | bool True | builtin-class bool | 9 | runtime | +| n_nesting.py:9 | ControlFlowNode for compile_ops | bool True | builtin-class bool | 8 | runtime | +| n_nesting.py:10 | ControlFlowNode for FunctionExpr | Function inner | builtin-class function | 10 | runtime | +| n_nesting.py:10 | ControlFlowNode for inner | Function inner | builtin-class function | 10 | runtime | +| n_nesting.py:13 | ControlFlowNode for FunctionExpr | Function inner | builtin-class function | 13 | runtime | +| n_nesting.py:13 | ControlFlowNode for inner | Function inner | builtin-class function | 13 | runtime | +| n_nesting.py:15 | ControlFlowNode for Dict | Dict | builtin-class dict | 15 | runtime | +| n_nesting.py:15 | ControlFlowNode for attrs | Dict | builtin-class dict | 15 | runtime | +| n_nesting.py:16 | ControlFlowNode for Str | 'inner' | builtin-class str | 16 | runtime | +| n_nesting.py:16 | ControlFlowNode for inner | Function inner | builtin-class function | 10 | runtime | +| n_nesting.py:16 | ControlFlowNode for inner | Function inner | builtin-class function | 13 | runtime | +| n_nesting.py:18 | ControlFlowNode for attrs | Dict | builtin-class dict | 15 | runtime | +| n_nesting.py:22 | ControlFlowNode for FunctionExpr | Function f1 | builtin-class function | 22 | import | +| n_nesting.py:22 | ControlFlowNode for f1 | Function f1 | builtin-class function | 22 | import | +| n_nesting.py:23 | ControlFlowNode for Attribute | int 1 | builtin-class int | 23 | code/n_nesting.py:25 from code/i_imports.py:38 from import | +| n_nesting.py:23 | ControlFlowNode for Attribute | int 1 | builtin-class int | 23 | code/n_nesting.py:25 from code/n_nesting.py:27 from code/n_nesting.py:29 from runtime | +| n_nesting.py:23 | ControlFlowNode for Attribute | int 1 | builtin-class int | 23 | code/n_nesting.py:25 from code/n_nesting.py:27 from runtime | +| n_nesting.py:23 | ControlFlowNode for Attribute | int 1 | builtin-class int | 23 | code/n_nesting.py:25 from runtime | +| n_nesting.py:23 | ControlFlowNode for Attribute | int 1 | builtin-class int | 23 | runtime | +| n_nesting.py:23 | ControlFlowNode for C | int 1 | builtin-class int | 34 | code/n_nesting.py:25 from code/i_imports.py:38 from import | +| n_nesting.py:23 | ControlFlowNode for C | int 1 | builtin-class int | 34 | code/n_nesting.py:25 from code/n_nesting.py:27 from code/n_nesting.py:29 from runtime | +| n_nesting.py:23 | ControlFlowNode for C | int 1 | builtin-class int | 34 | code/n_nesting.py:25 from code/n_nesting.py:27 from runtime | +| n_nesting.py:23 | ControlFlowNode for C | int 1 | builtin-class int | 34 | code/n_nesting.py:25 from runtime | +| n_nesting.py:23 | ControlFlowNode for C | int 1 | builtin-class int | 34 | runtime | +| n_nesting.py:23 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 23 | code/n_nesting.py:25 from code/i_imports.py:38 from import | +| n_nesting.py:23 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 23 | code/n_nesting.py:25 from code/n_nesting.py:27 from code/n_nesting.py:29 from runtime | +| n_nesting.py:23 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 23 | code/n_nesting.py:25 from code/n_nesting.py:27 from runtime | +| n_nesting.py:23 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 23 | code/n_nesting.py:25 from runtime | +| n_nesting.py:23 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 23 | runtime | +| n_nesting.py:24 | ControlFlowNode for FunctionExpr | Function f2 | builtin-class function | 24 | import | +| n_nesting.py:24 | ControlFlowNode for f2 | Function f2 | builtin-class function | 24 | import | +| n_nesting.py:25 | ControlFlowNode for f1 | Function f1 | builtin-class function | 22 | code/i_imports.py:38 from import | +| n_nesting.py:25 | ControlFlowNode for f1 | Function f1 | builtin-class function | 22 | code/n_nesting.py:27 from code/n_nesting.py:29 from code/n_nesting.py:31 from import | +| n_nesting.py:25 | ControlFlowNode for f1 | Function f1 | builtin-class function | 22 | code/n_nesting.py:27 from code/n_nesting.py:29 from runtime | +| n_nesting.py:25 | ControlFlowNode for f1 | Function f1 | builtin-class function | 22 | code/n_nesting.py:27 from runtime | +| n_nesting.py:25 | ControlFlowNode for f1 | Function f1 | builtin-class function | 22 | runtime | +| n_nesting.py:25 | ControlFlowNode for f1() | NoneType None | builtin-class NoneType | 22 | code/i_imports.py:38 from import | +| n_nesting.py:25 | ControlFlowNode for f1() | NoneType None | builtin-class NoneType | 22 | code/n_nesting.py:27 from code/n_nesting.py:29 from code/n_nesting.py:31 from import | +| n_nesting.py:25 | ControlFlowNode for f1() | NoneType None | builtin-class NoneType | 22 | code/n_nesting.py:27 from code/n_nesting.py:29 from runtime | +| n_nesting.py:25 | ControlFlowNode for f1() | NoneType None | builtin-class NoneType | 22 | code/n_nesting.py:27 from runtime | +| n_nesting.py:25 | ControlFlowNode for f1() | NoneType None | builtin-class NoneType | 22 | runtime | +| n_nesting.py:26 | ControlFlowNode for FunctionExpr | Function f3 | builtin-class function | 26 | import | +| n_nesting.py:26 | ControlFlowNode for f3 | Function f3 | builtin-class function | 26 | import | +| n_nesting.py:27 | ControlFlowNode for f2 | Function f2 | builtin-class function | 24 | code/n_nesting.py:29 from code/n_nesting.py:31 from import | +| n_nesting.py:27 | ControlFlowNode for f2 | Function f2 | builtin-class function | 24 | code/n_nesting.py:29 from runtime | +| n_nesting.py:27 | ControlFlowNode for f2 | Function f2 | builtin-class function | 24 | runtime | +| n_nesting.py:27 | ControlFlowNode for f2() | NoneType None | builtin-class NoneType | 24 | code/n_nesting.py:29 from code/n_nesting.py:31 from import | +| n_nesting.py:27 | ControlFlowNode for f2() | NoneType None | builtin-class NoneType | 24 | code/n_nesting.py:29 from runtime | +| n_nesting.py:27 | ControlFlowNode for f2() | NoneType None | builtin-class NoneType | 24 | runtime | +| n_nesting.py:28 | ControlFlowNode for FunctionExpr | Function f4 | builtin-class function | 28 | import | +| n_nesting.py:28 | ControlFlowNode for f4 | Function f4 | builtin-class function | 28 | import | +| n_nesting.py:29 | ControlFlowNode for f3 | Function f3 | builtin-class function | 26 | code/n_nesting.py:31 from import | +| n_nesting.py:29 | ControlFlowNode for f3 | Function f3 | builtin-class function | 26 | runtime | +| n_nesting.py:29 | ControlFlowNode for f3() | NoneType None | builtin-class NoneType | 26 | code/n_nesting.py:31 from import | +| n_nesting.py:29 | ControlFlowNode for f3() | NoneType None | builtin-class NoneType | 26 | runtime | +| n_nesting.py:30 | ControlFlowNode for C | class C | builtin-class type | 30 | import | +| n_nesting.py:30 | ControlFlowNode for ClassExpr | class C | builtin-class type | 30 | import | +| n_nesting.py:30 | ControlFlowNode for object | builtin-class object | builtin-class type | 30 | import | +| n_nesting.py:31 | ControlFlowNode for f4 | Function f4 | builtin-class function | 28 | import | +| n_nesting.py:31 | ControlFlowNode for f4() | NoneType None | builtin-class NoneType | 28 | import | +| n_nesting.py:32 | ControlFlowNode for C | class C | builtin-class type | 30 | import | +| n_nesting.py:32 | ControlFlowNode for ClassExpr | class D | builtin-class type | 32 | import | +| n_nesting.py:32 | ControlFlowNode for D | class D | builtin-class type | 32 | import | +| n_nesting.py:34 | ControlFlowNode for C | int 1 | builtin-class int | 34 | import | +| n_nesting.py:34 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 34 | import | +| p_decorators.py:3 | ControlFlowNode for FunctionExpr | Function simple | builtin-class function | 3 | import | +| p_decorators.py:3 | ControlFlowNode for simple | Function simple | builtin-class function | 3 | import | +| p_decorators.py:4 | ControlFlowNode for Attribute | 'Hello' | builtin-class str | 4 | code/p_decorators.py:7 from import | +| p_decorators.py:4 | ControlFlowNode for Attribute | 'Hello' | builtin-class str | 4 | runtime | +| p_decorators.py:4 | ControlFlowNode for Str | 'Hello' | builtin-class str | 4 | code/p_decorators.py:7 from import | +| p_decorators.py:4 | ControlFlowNode for Str | 'Hello' | builtin-class str | 4 | runtime | +| p_decorators.py:4 | ControlFlowNode for func | Function foo | builtin-class function | 8 | code/p_decorators.py:7 from import | +| p_decorators.py:5 | ControlFlowNode for func | Function foo | builtin-class function | 8 | code/p_decorators.py:7 from import | +| p_decorators.py:7 | ControlFlowNode for simple | Function simple | builtin-class function | 3 | import | +| p_decorators.py:7 | ControlFlowNode for simple() | Function foo | builtin-class function | 8 | import | +| p_decorators.py:8 | ControlFlowNode for FunctionExpr | Function foo | builtin-class function | 8 | import | +| p_decorators.py:8 | ControlFlowNode for foo | Function foo | builtin-class function | 8 | import | +| p_decorators.py:11 | ControlFlowNode for FunctionExpr | Function complex | builtin-class function | 11 | import | +| p_decorators.py:11 | ControlFlowNode for complex | Function complex | builtin-class function | 11 | import | +| p_decorators.py:12 | ControlFlowNode for FunctionExpr | Function annotate | builtin-class function | 12 | code/p_decorators.py:17 from import | +| p_decorators.py:12 | ControlFlowNode for FunctionExpr | Function annotate | builtin-class function | 12 | runtime | +| p_decorators.py:12 | ControlFlowNode for annotate | Function annotate | builtin-class function | 12 | code/p_decorators.py:17 from import | +| p_decorators.py:12 | ControlFlowNode for annotate | Function annotate | builtin-class function | 12 | runtime | +| p_decorators.py:13 | ControlFlowNode for func | Function bar | builtin-class function | 18 | code/p_decorators.py:17 from import | +| p_decorators.py:14 | ControlFlowNode for func | Function bar | builtin-class function | 18 | code/p_decorators.py:17 from import | +| p_decorators.py:15 | ControlFlowNode for annotate | Function annotate | builtin-class function | 12 | code/p_decorators.py:17 from import | +| p_decorators.py:15 | ControlFlowNode for annotate | Function annotate | builtin-class function | 12 | runtime | +| p_decorators.py:17 | ControlFlowNode for Str | 'Hi' | builtin-class str | 17 | import | +| p_decorators.py:17 | ControlFlowNode for complex | Function complex | builtin-class function | 11 | import | +| p_decorators.py:17 | ControlFlowNode for complex() | Function annotate | builtin-class function | 12 | import | +| p_decorators.py:17 | ControlFlowNode for complex()() | Function bar | builtin-class function | 18 | import | +| p_decorators.py:18 | ControlFlowNode for FunctionExpr | Function bar | builtin-class function | 18 | import | +| p_decorators.py:18 | ControlFlowNode for bar | Function bar | builtin-class function | 18 | import | +| p_decorators.py:21 | ControlFlowNode for foo | Function foo | builtin-class function | 8 | import | +| p_decorators.py:22 | ControlFlowNode for bar | Function bar | builtin-class function | 18 | import | +| p_decorators.py:24 | ControlFlowNode for C | class C | builtin-class type | 24 | import | +| p_decorators.py:24 | ControlFlowNode for ClassExpr | class C | builtin-class type | 24 | import | +| p_decorators.py:24 | ControlFlowNode for object | builtin-class object | builtin-class type | 24 | import | +| p_decorators.py:26 | ControlFlowNode for staticmethod | builtin-class staticmethod | builtin-class type | 26 | import | +| p_decorators.py:26 | ControlFlowNode for staticmethod() | staticmethod() | builtin-class staticmethod | 26 | import | +| p_decorators.py:27 | ControlFlowNode for FunctionExpr | Function smeth | builtin-class function | 27 | import | +| p_decorators.py:27 | ControlFlowNode for smeth | staticmethod() | builtin-class staticmethod | 26 | import | +| p_decorators.py:31 | ControlFlowNode for classmethod | builtin-class classmethod | builtin-class type | 31 | import | +| p_decorators.py:31 | ControlFlowNode for classmethod() | classmethod() | builtin-class classmethod | 31 | import | +| p_decorators.py:32 | ControlFlowNode for FunctionExpr | Function cmeth | builtin-class function | 32 | import | +| p_decorators.py:32 | ControlFlowNode for cmeth | classmethod() | builtin-class classmethod | 31 | import | +| q_super.py:1 | ControlFlowNode for Base2 | class Base2 | builtin-class type | 1 | import | +| q_super.py:1 | ControlFlowNode for ClassExpr | class Base2 | builtin-class type | 1 | import | +| q_super.py:1 | ControlFlowNode for object | builtin-class object | builtin-class type | 1 | import | +| q_super.py:3 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 3 | import | +| q_super.py:3 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 3 | import | +| q_super.py:4 | ControlFlowNode for Attribute | super().__init__ | builtin-class method | 4 | code/q_super.py:12 from runtime | +| q_super.py:4 | ControlFlowNode for Attribute | super().__init__ | builtin-class method | 4 | runtime | +| q_super.py:4 | ControlFlowNode for Base2 | class Base2 | builtin-class type | 1 | code/q_super.py:12 from runtime | +| q_super.py:4 | ControlFlowNode for Base2 | class Base2 | builtin-class type | 1 | runtime | +| q_super.py:4 | ControlFlowNode for self | self | class Base2 | 3 | runtime | +| q_super.py:4 | ControlFlowNode for self | self | class Derived4 | 10 | code/q_super.py:12 from runtime | +| q_super.py:4 | ControlFlowNode for super | builtin-class super | builtin-class type | 4 | code/q_super.py:12 from runtime | +| q_super.py:4 | ControlFlowNode for super | builtin-class super | builtin-class type | 4 | runtime | +| q_super.py:4 | ControlFlowNode for super() | super() | builtin-class super | 4 | code/q_super.py:12 from runtime | +| q_super.py:4 | ControlFlowNode for super() | super() | builtin-class super | 4 | runtime | +| q_super.py:8 | ControlFlowNode for Base2 | class Base2 | builtin-class type | 1 | import | +| q_super.py:8 | ControlFlowNode for ClassExpr | class Derived4 | builtin-class type | 8 | import | +| q_super.py:8 | ControlFlowNode for Derived4 | class Derived4 | builtin-class type | 8 | import | +| q_super.py:10 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 10 | import | +| q_super.py:10 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 10 | import | +| q_super.py:11 | ControlFlowNode for Base2 | class Base2 | builtin-class type | 1 | runtime | +| q_super.py:11 | ControlFlowNode for self | self | class Derived4 | 10 | runtime | +| q_super.py:11 | ControlFlowNode for super | builtin-class super | builtin-class type | 11 | runtime | +| q_super.py:11 | ControlFlowNode for super() | super() | builtin-class super | 11 | runtime | +| q_super.py:12 | ControlFlowNode for Attribute | super().__init__ | builtin-class method | 12 | runtime | +| q_super.py:12 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 3 | runtime | +| q_super.py:12 | ControlFlowNode for Derived4 | class Derived4 | builtin-class type | 8 | runtime | +| q_super.py:12 | ControlFlowNode for self | self | class Derived4 | 10 | runtime | +| q_super.py:12 | ControlFlowNode for super | builtin-class super | builtin-class type | 12 | runtime | +| q_super.py:12 | ControlFlowNode for super() | super() | builtin-class super | 12 | runtime | +| q_super.py:14 | ControlFlowNode for Base1 | class Base1 | builtin-class type | 14 | import | +| q_super.py:14 | ControlFlowNode for ClassExpr | class Base1 | builtin-class type | 14 | import | +| q_super.py:14 | ControlFlowNode for object | builtin-class object | builtin-class type | 14 | import | +| q_super.py:16 | ControlFlowNode for FunctionExpr | Function meth | builtin-class function | 16 | import | +| q_super.py:16 | ControlFlowNode for meth | Function meth | builtin-class function | 16 | import | +| q_super.py:17 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 17 | code/q_super.py:22 from code/q_super.py:27 from code/q_super.py:38 from runtime | +| q_super.py:17 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 17 | code/q_super.py:22 from code/q_super.py:27 from runtime | +| q_super.py:17 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 17 | code/q_super.py:22 from code/q_super.py:32 from runtime | +| q_super.py:17 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 17 | code/q_super.py:22 from runtime | +| q_super.py:17 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 17 | runtime | +| q_super.py:19 | ControlFlowNode for Base1 | class Base1 | builtin-class type | 14 | import | +| q_super.py:19 | ControlFlowNode for ClassExpr | class Derived1 | builtin-class type | 19 | import | +| q_super.py:19 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | 19 | import | +| q_super.py:21 | ControlFlowNode for FunctionExpr | Function meth | builtin-class function | 21 | import | +| q_super.py:21 | ControlFlowNode for meth | Function meth | builtin-class function | 21 | import | +| q_super.py:22 | ControlFlowNode for Attribute | super().meth | builtin-class method | 22 | code/q_super.py:27 from code/q_super.py:38 from runtime | +| q_super.py:22 | ControlFlowNode for Attribute | super().meth | builtin-class method | 22 | code/q_super.py:27 from runtime | +| q_super.py:22 | ControlFlowNode for Attribute | super().meth | builtin-class method | 22 | code/q_super.py:32 from runtime | +| q_super.py:22 | ControlFlowNode for Attribute | super().meth | builtin-class method | 22 | runtime | +| q_super.py:22 | ControlFlowNode for Attribute() | int 7 | builtin-class int | 17 | code/q_super.py:27 from code/q_super.py:38 from runtime | +| q_super.py:22 | ControlFlowNode for Attribute() | int 7 | builtin-class int | 17 | code/q_super.py:27 from runtime | +| q_super.py:22 | ControlFlowNode for Attribute() | int 7 | builtin-class int | 17 | code/q_super.py:32 from runtime | +| q_super.py:22 | ControlFlowNode for Attribute() | int 7 | builtin-class int | 17 | runtime | +| q_super.py:22 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | 19 | code/q_super.py:27 from code/q_super.py:38 from runtime | +| q_super.py:22 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | 19 | code/q_super.py:27 from runtime | +| q_super.py:22 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | 19 | code/q_super.py:32 from runtime | +| q_super.py:22 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | 19 | runtime | +| q_super.py:22 | ControlFlowNode for self | self | class Derived1 | 21 | runtime | +| q_super.py:22 | ControlFlowNode for self | self | class Derived2 | 26 | code/q_super.py:27 from runtime | +| q_super.py:22 | ControlFlowNode for self | self | class Derived5 | 31 | code/q_super.py:32 from runtime | +| q_super.py:22 | ControlFlowNode for self | self | class Wrong1 | 37 | code/q_super.py:27 from code/q_super.py:38 from runtime | +| q_super.py:22 | ControlFlowNode for super | builtin-class super | builtin-class type | 22 | code/q_super.py:27 from code/q_super.py:38 from runtime | +| q_super.py:22 | ControlFlowNode for super | builtin-class super | builtin-class type | 22 | code/q_super.py:27 from runtime | +| q_super.py:22 | ControlFlowNode for super | builtin-class super | builtin-class type | 22 | code/q_super.py:32 from runtime | +| q_super.py:22 | ControlFlowNode for super | builtin-class super | builtin-class type | 22 | runtime | +| q_super.py:22 | ControlFlowNode for super() | super() | builtin-class super | 22 | code/q_super.py:27 from code/q_super.py:38 from runtime | +| q_super.py:22 | ControlFlowNode for super() | super() | builtin-class super | 22 | code/q_super.py:27 from runtime | +| q_super.py:22 | ControlFlowNode for super() | super() | builtin-class super | 22 | code/q_super.py:32 from runtime | +| q_super.py:22 | ControlFlowNode for super() | super() | builtin-class super | 22 | runtime | +| q_super.py:24 | ControlFlowNode for ClassExpr | class Derived2 | builtin-class type | 24 | import | +| q_super.py:24 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | 19 | import | +| q_super.py:24 | ControlFlowNode for Derived2 | class Derived2 | builtin-class type | 24 | import | +| q_super.py:26 | ControlFlowNode for FunctionExpr | Function meth | builtin-class function | 26 | import | +| q_super.py:26 | ControlFlowNode for meth | Function meth | builtin-class function | 26 | import | +| q_super.py:27 | ControlFlowNode for Attribute | super().meth | builtin-class method | 27 | code/q_super.py:38 from runtime | +| q_super.py:27 | ControlFlowNode for Attribute | super().meth | builtin-class method | 27 | runtime | +| q_super.py:27 | ControlFlowNode for Attribute() | int 7 | builtin-class int | 17 | code/q_super.py:38 from runtime | +| q_super.py:27 | ControlFlowNode for Attribute() | int 7 | builtin-class int | 17 | runtime | +| q_super.py:27 | ControlFlowNode for Derived2 | class Derived2 | builtin-class type | 24 | code/q_super.py:38 from runtime | +| q_super.py:27 | ControlFlowNode for Derived2 | class Derived2 | builtin-class type | 24 | runtime | +| q_super.py:27 | ControlFlowNode for self | self | class Derived2 | 26 | runtime | +| q_super.py:27 | ControlFlowNode for self | self | class Wrong1 | 37 | code/q_super.py:38 from runtime | +| q_super.py:27 | ControlFlowNode for super | builtin-class super | builtin-class type | 27 | code/q_super.py:38 from runtime | +| q_super.py:27 | ControlFlowNode for super | builtin-class super | builtin-class type | 27 | runtime | +| q_super.py:27 | ControlFlowNode for super() | super() | builtin-class super | 27 | code/q_super.py:38 from runtime | +| q_super.py:27 | ControlFlowNode for super() | super() | builtin-class super | 27 | runtime | +| q_super.py:29 | ControlFlowNode for ClassExpr | class Derived5 | builtin-class type | 29 | import | +| q_super.py:29 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | 19 | import | +| q_super.py:29 | ControlFlowNode for Derived5 | class Derived5 | builtin-class type | 29 | import | +| q_super.py:31 | ControlFlowNode for FunctionExpr | Function meth | builtin-class function | 31 | import | +| q_super.py:31 | ControlFlowNode for meth | Function meth | builtin-class function | 31 | import | +| q_super.py:32 | ControlFlowNode for Attribute | super().meth | builtin-class method | 32 | runtime | +| q_super.py:32 | ControlFlowNode for Attribute() | int 7 | builtin-class int | 17 | runtime | +| q_super.py:32 | ControlFlowNode for Derived5 | class Derived5 | builtin-class type | 29 | runtime | +| q_super.py:32 | ControlFlowNode for self | self | class Derived5 | 31 | runtime | +| q_super.py:32 | ControlFlowNode for super | builtin-class super | builtin-class type | 32 | runtime | +| q_super.py:32 | ControlFlowNode for super() | super() | builtin-class super | 32 | runtime | +| q_super.py:35 | ControlFlowNode for ClassExpr | class Wrong1 | builtin-class type | 35 | import | +| q_super.py:35 | ControlFlowNode for Derived2 | class Derived2 | builtin-class type | 24 | import | +| q_super.py:35 | ControlFlowNode for Derived5 | class Derived5 | builtin-class type | 29 | import | +| q_super.py:35 | ControlFlowNode for Wrong1 | class Wrong1 | builtin-class type | 35 | import | +| q_super.py:37 | ControlFlowNode for FunctionExpr | Function meth | builtin-class function | 37 | import | +| q_super.py:37 | ControlFlowNode for meth | Function meth | builtin-class function | 37 | import | +| q_super.py:38 | ControlFlowNode for Attribute | super().meth | builtin-class method | 38 | runtime | +| q_super.py:38 | ControlFlowNode for Attribute() | int 7 | builtin-class int | 17 | runtime | +| q_super.py:38 | ControlFlowNode for Derived5 | class Derived5 | builtin-class type | 29 | runtime | +| q_super.py:38 | ControlFlowNode for self | self | class Wrong1 | 37 | runtime | +| q_super.py:38 | ControlFlowNode for super | builtin-class super | builtin-class type | 38 | runtime | +| q_super.py:38 | ControlFlowNode for super() | super() | builtin-class super | 38 | runtime | +| q_super.py:41 | ControlFlowNode for ClassExpr | class DA | builtin-class type | 41 | import | +| q_super.py:41 | ControlFlowNode for DA | class DA | builtin-class type | 41 | import | +| q_super.py:41 | ControlFlowNode for object | builtin-class object | builtin-class type | 41 | import | +| q_super.py:43 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 43 | import | +| q_super.py:43 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 43 | import | +| q_super.py:46 | ControlFlowNode for ClassExpr | class DB | builtin-class type | 46 | import | +| q_super.py:46 | ControlFlowNode for DA | class DA | builtin-class type | 41 | import | +| q_super.py:46 | ControlFlowNode for DB | class DB | builtin-class type | 46 | import | +| q_super.py:48 | ControlFlowNode for ClassExpr | class DC | builtin-class type | 48 | import | +| q_super.py:48 | ControlFlowNode for DA | class DA | builtin-class type | 41 | import | +| q_super.py:48 | ControlFlowNode for DC | class DC | builtin-class type | 48 | import | +| q_super.py:50 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 50 | import | +| q_super.py:50 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 50 | import | +| q_super.py:51 | ControlFlowNode for Attribute | class DC | builtin-class type | 48 | runtime | +| q_super.py:51 | ControlFlowNode for DB | class DB | builtin-class type | 46 | runtime | +| q_super.py:51 | ControlFlowNode for self | self | class DC | 50 | runtime | +| q_super.py:51 | ControlFlowNode for sup | super() | builtin-class super | 51 | runtime | +| q_super.py:51 | ControlFlowNode for super | builtin-class super | builtin-class type | 51 | runtime | +| q_super.py:51 | ControlFlowNode for super() | super() | builtin-class super | 51 | runtime | +| q_super.py:52 | ControlFlowNode for Attribute | super().__init__ | builtin-class method | 52 | runtime | +| q_super.py:52 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 43 | runtime | +| q_super.py:52 | ControlFlowNode for sup | super() | builtin-class super | 51 | runtime | +| q_super.py:55 | ControlFlowNode for ClassExpr | class DD | builtin-class type | 55 | import | +| q_super.py:55 | ControlFlowNode for DA | class DA | builtin-class type | 41 | import | +| q_super.py:55 | ControlFlowNode for DD | class DD | builtin-class type | 55 | import | +| q_super.py:57 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 57 | import | +| q_super.py:57 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 57 | import | +| q_super.py:58 | ControlFlowNode for DD | class DD | builtin-class type | 55 | runtime | +| q_super.py:58 | ControlFlowNode for self | self | class DD | 57 | runtime | +| q_super.py:58 | ControlFlowNode for sup | super() | builtin-class super | 58 | runtime | +| q_super.py:58 | ControlFlowNode for super | builtin-class super | builtin-class type | 58 | runtime | +| q_super.py:58 | ControlFlowNode for super() | super() | builtin-class super | 58 | runtime | +| q_super.py:59 | ControlFlowNode for Attribute | super().__init__ | builtin-class method | 59 | runtime | +| q_super.py:59 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 43 | runtime | +| q_super.py:59 | ControlFlowNode for sup | super() | builtin-class super | 58 | runtime | +| q_super.py:61 | ControlFlowNode for ClassExpr | class DE | builtin-class type | 61 | import | +| q_super.py:61 | ControlFlowNode for DA | class DA | builtin-class type | 41 | import | +| q_super.py:61 | ControlFlowNode for DE | class DE | builtin-class type | 61 | import | +| q_super.py:63 | ControlFlowNode for ClassExpr | class DF | builtin-class type | 63 | import | +| q_super.py:63 | ControlFlowNode for DA | class DA | builtin-class type | 41 | import | +| q_super.py:63 | ControlFlowNode for DF | class DF | builtin-class type | 63 | import | +| q_super.py:65 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 65 | import | +| q_super.py:65 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 65 | import | +| q_super.py:66 | ControlFlowNode for Attribute | class DF | builtin-class type | 63 | runtime | +| q_super.py:66 | ControlFlowNode for Attribute | super().__init__ | builtin-class method | 66 | runtime | +| q_super.py:66 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 43 | runtime | +| q_super.py:66 | ControlFlowNode for DE | class DE | builtin-class type | 61 | runtime | +| q_super.py:66 | ControlFlowNode for self | self | class DF | 65 | runtime | +| q_super.py:66 | ControlFlowNode for super | builtin-class super | builtin-class type | 66 | runtime | +| q_super.py:66 | ControlFlowNode for super() | super() | builtin-class super | 66 | runtime | +| q_super.py:68 | ControlFlowNode for ClassExpr | class N | builtin-class type | 68 | import | +| q_super.py:68 | ControlFlowNode for N | class N | builtin-class type | 68 | import | +| q_super.py:68 | ControlFlowNode for object | builtin-class object | builtin-class type | 68 | import | +| q_super.py:71 | ControlFlowNode for ClassExpr | class M | builtin-class type | 71 | import | +| q_super.py:71 | ControlFlowNode for M | class M | builtin-class type | 71 | import | +| q_super.py:71 | ControlFlowNode for N | class N | builtin-class type | 68 | import | +| q_super.py:73 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 73 | import | +| q_super.py:73 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 73 | import | +| q_super.py:74 | ControlFlowNode for M | class M | builtin-class type | 71 | runtime | +| q_super.py:74 | ControlFlowNode for s | super() | builtin-class super | 74 | runtime | +| q_super.py:74 | ControlFlowNode for self | self | class M | 73 | runtime | +| q_super.py:74 | ControlFlowNode for super | builtin-class super | builtin-class type | 74 | runtime | +| q_super.py:74 | ControlFlowNode for super() | super() | builtin-class super | 74 | runtime | +| q_super.py:75 | ControlFlowNode for Attribute | super().__init__ | builtin-class method | 75 | runtime | +| q_super.py:75 | ControlFlowNode for i | super().__init__ | builtin-class method | 75 | runtime | +| q_super.py:75 | ControlFlowNode for s | super() | builtin-class super | 74 | runtime | +| q_super.py:76 | ControlFlowNode for i | super().__init__ | builtin-class method | 75 | runtime | +| r_regressions.py:5 | ControlFlowNode for ClassExpr | class Queue | builtin-class type | 5 | import | +| r_regressions.py:5 | ControlFlowNode for Queue | class Queue | builtin-class type | 5 | import | +| r_regressions.py:5 | ControlFlowNode for object | builtin-class object | builtin-class type | 5 | import | +| r_regressions.py:7 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 7 | import | +| r_regressions.py:7 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 7 | import | +| r_regressions.py:9 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 11 | runtime | +| r_regressions.py:9 | ControlFlowNode for self | self | class Queue | 7 | runtime | +| r_regressions.py:11 | ControlFlowNode for FunctionExpr | Function _after_fork | builtin-class function | 11 | import | +| r_regressions.py:11 | ControlFlowNode for _after_fork | Function _after_fork | builtin-class function | 11 | import | +| r_regressions.py:12 | ControlFlowNode for Attribute | bool False | builtin-class bool | 12 | code/r_regressions.py:9 from runtime | +| r_regressions.py:12 | ControlFlowNode for False | bool False | builtin-class bool | 12 | code/r_regressions.py:9 from runtime | +| r_regressions.py:12 | ControlFlowNode for self | self | class Queue | 7 | code/r_regressions.py:9 from runtime | +| r_regressions.py:13 | ControlFlowNode for Attribute | NoneType None | builtin-class NoneType | 13 | code/r_regressions.py:9 from runtime | +| r_regressions.py:13 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 13 | code/r_regressions.py:9 from runtime | +| r_regressions.py:13 | ControlFlowNode for self | self | class Queue | 7 | code/r_regressions.py:9 from runtime | +| r_regressions.py:15 | ControlFlowNode for FunctionExpr | Function close | builtin-class function | 15 | import | +| r_regressions.py:15 | ControlFlowNode for close | Function close | builtin-class function | 15 | import | +| r_regressions.py:16 | ControlFlowNode for Attribute | bool True | builtin-class bool | 16 | runtime | +| r_regressions.py:16 | ControlFlowNode for True | bool True | builtin-class bool | 16 | runtime | +| r_regressions.py:16 | ControlFlowNode for self | self | class Queue | 15 | runtime | +| r_regressions.py:18 | ControlFlowNode for self | self | class Queue | 15 | runtime | +| r_regressions.py:20 | ControlFlowNode for Attribute | NoneType None | builtin-class NoneType | 13 | runtime | +| r_regressions.py:20 | ControlFlowNode for close | NoneType None | builtin-class NoneType | 13 | runtime | +| r_regressions.py:20 | ControlFlowNode for self | self | class Queue | 15 | runtime | +| r_regressions.py:21 | ControlFlowNode for close | NoneType None | builtin-class NoneType | 13 | runtime | +| r_regressions.py:22 | ControlFlowNode for Attribute | NoneType None | builtin-class NoneType | 22 | runtime | +| r_regressions.py:22 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 22 | runtime | +| r_regressions.py:22 | ControlFlowNode for self | self | class Queue | 15 | runtime | +| r_regressions.py:27 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 27 | import | +| r_regressions.py:27 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 27 | import | +| r_regressions.py:27 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 27 | import | +| r_regressions.py:27 | ControlFlowNode for f | Function f | builtin-class function | 27 | import | +| r_regressions.py:31 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 27 | runtime | +| r_regressions.py:33 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 27 | runtime | +| r_regressions.py:35 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 35 | runtime | +| r_regressions.py:35 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 35 | runtime | +| r_regressions.py:36 | ControlFlowNode for z | int 0 | builtin-class int | 27 | runtime | +| r_regressions.py:42 | ControlFlowNode for FunctionExpr | Function find_library | builtin-class function | 42 | import | +| r_regressions.py:42 | ControlFlowNode for find_library | Function find_library | builtin-class function | 42 | import | +| r_regressions.py:43 | ControlFlowNode for List | List | builtin-class list | 43 | runtime | +| r_regressions.py:46 | ControlFlowNode for FunctionExpr | Function fail | builtin-class function | 46 | import | +| r_regressions.py:46 | ControlFlowNode for fail | Function fail | builtin-class function | 46 | import | +| r_regressions.py:49 | ControlFlowNode for C | class C | builtin-class type | 49 | import | +| r_regressions.py:49 | ControlFlowNode for ClassExpr | class C | builtin-class type | 49 | import | +| r_regressions.py:49 | ControlFlowNode for object | builtin-class object | builtin-class type | 49 | import | +| r_regressions.py:51 | ControlFlowNode for FunctionExpr | Function fail | builtin-class function | 51 | import | +| r_regressions.py:51 | ControlFlowNode for fail | Function fail | builtin-class function | 51 | import | +| r_regressions.py:52 | ControlFlowNode for fail | Function fail | builtin-class function | 46 | runtime | +| r_regressions.py:52 | ControlFlowNode for fail() | NoneType None | builtin-class NoneType | 46 | runtime | +| r_regressions.py:58 | ControlFlowNode for FunctionExpr | Function method_decorator | builtin-class function | 58 | import | +| r_regressions.py:58 | ControlFlowNode for Str | '' | builtin-class str | 58 | import | +| r_regressions.py:58 | ControlFlowNode for method_decorator | Function method_decorator | builtin-class function | 58 | import | +| r_regressions.py:61 | ControlFlowNode for FunctionExpr | Function _dec | builtin-class function | 61 | code/r_regressions.py:85 from import | +| r_regressions.py:61 | ControlFlowNode for FunctionExpr | Function _dec | builtin-class function | 61 | runtime | +| r_regressions.py:61 | ControlFlowNode for _dec | Function _dec | builtin-class function | 61 | code/r_regressions.py:85 from import | +| r_regressions.py:61 | ControlFlowNode for _dec | Function _dec | builtin-class function | 61 | runtime | +| r_regressions.py:62 | ControlFlowNode for is_class | bool True | builtin-class bool | 62 | code/r_regressions.py:85 from import | +| r_regressions.py:62 | ControlFlowNode for isinstance | Builtin-function isinstance | builtin-class builtin_function_or_method | 62 | code/r_regressions.py:85 from import | +| r_regressions.py:62 | ControlFlowNode for isinstance() | bool True | builtin-class bool | 62 | code/r_regressions.py:85 from import | +| r_regressions.py:62 | ControlFlowNode for obj | class TestFirst | builtin-class type | 86 | code/r_regressions.py:85 from import | +| r_regressions.py:62 | ControlFlowNode for type | builtin-class type | builtin-class type | 62 | code/r_regressions.py:85 from import | +| r_regressions.py:63 | ControlFlowNode for is_class | bool True | builtin-class bool | 62 | code/r_regressions.py:85 from import | +| r_regressions.py:68 | ControlFlowNode for FunctionExpr | Function _wrapper | builtin-class function | 68 | code/r_regressions.py:85 from import | +| r_regressions.py:68 | ControlFlowNode for _wrapper | Function _wrapper | builtin-class function | 68 | code/r_regressions.py:85 from import | +| r_regressions.py:72 | ControlFlowNode for is_class | bool True | builtin-class bool | 62 | code/r_regressions.py:85 from import | +| r_regressions.py:73 | ControlFlowNode for _wrapper | Function _wrapper | builtin-class function | 68 | code/r_regressions.py:85 from import | +| r_regressions.py:73 | ControlFlowNode for obj | class TestFirst | builtin-class type | 86 | code/r_regressions.py:85 from import | +| r_regressions.py:73 | ControlFlowNode for setattr | Builtin-function setattr | builtin-class builtin_function_or_method | 73 | code/r_regressions.py:85 from import | +| r_regressions.py:73 | ControlFlowNode for setattr() | NoneType None | builtin-class NoneType | 73 | code/r_regressions.py:85 from import | +| r_regressions.py:74 | ControlFlowNode for obj | class TestFirst | builtin-class type | 86 | code/r_regressions.py:85 from import | +| r_regressions.py:78 | ControlFlowNode for _dec | Function _dec | builtin-class function | 61 | code/r_regressions.py:85 from import | +| r_regressions.py:78 | ControlFlowNode for _dec | Function _dec | builtin-class function | 61 | runtime | +| r_regressions.py:80 | ControlFlowNode for FunctionExpr | Function deco | builtin-class function | 80 | import | +| r_regressions.py:80 | ControlFlowNode for deco | Function deco | builtin-class function | 80 | import | +| r_regressions.py:81 | ControlFlowNode for FunctionExpr | Function _wrapper | builtin-class function | 81 | runtime | +| r_regressions.py:81 | ControlFlowNode for _wrapper | Function _wrapper | builtin-class function | 81 | runtime | +| r_regressions.py:83 | ControlFlowNode for _wrapper | Function _wrapper | builtin-class function | 81 | runtime | +| r_regressions.py:85 | ControlFlowNode for Str | 'method' | builtin-class str | 85 | import | +| r_regressions.py:85 | ControlFlowNode for deco | Function deco | builtin-class function | 80 | import | +| r_regressions.py:85 | ControlFlowNode for method_decorator | Function method_decorator | builtin-class function | 58 | import | +| r_regressions.py:85 | ControlFlowNode for method_decorator() | Function _dec | builtin-class function | 61 | import | +| r_regressions.py:85 | ControlFlowNode for method_decorator()() | class TestFirst | builtin-class type | 86 | import | +| r_regressions.py:86 | ControlFlowNode for ClassExpr | class TestFirst | builtin-class type | 86 | import | +| r_regressions.py:86 | ControlFlowNode for TestFirst | class TestFirst | builtin-class type | 86 | import | +| r_regressions.py:86 | ControlFlowNode for object | builtin-class object | builtin-class type | 86 | import | +| r_regressions.py:87 | ControlFlowNode for FunctionExpr | Function method | builtin-class function | 87 | import | +| r_regressions.py:87 | ControlFlowNode for method | Function method | builtin-class function | 87 | import | +| r_regressions.py:88 | ControlFlowNode for Str | 'hello world' | builtin-class str | 88 | code/r_regressions.py:90 from import | +| r_regressions.py:88 | ControlFlowNode for Str | 'hello world' | builtin-class str | 88 | runtime | +| r_regressions.py:90 | ControlFlowNode for Attribute() | 'hello world' | builtin-class str | 88 | import | +| r_regressions.py:90 | ControlFlowNode for TestFirst | class TestFirst | builtin-class type | 86 | import | +| r_regressions.py:90 | ControlFlowNode for TestFirst() | TestFirst() | class TestFirst | 90 | import | +| r_regressions.py:93 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | 93 | import | +| r_regressions.py:93 | ControlFlowNode for sys | Module sys | builtin-class module | 93 | import | +| r_regressions.py:95 | ControlFlowNode for Attribute | tuple object | builtin-class tuple | 95 | import | +| r_regressions.py:95 | ControlFlowNode for _names | tuple object | builtin-class tuple | 95 | import | +| r_regressions.py:95 | ControlFlowNode for sys | Module sys | builtin-class module | 93 | import | +| r_regressions.py:97 | ControlFlowNode for Compare | bool True | builtin-class bool | 97 | import | +| r_regressions.py:97 | ControlFlowNode for Str | 'time' | builtin-class str | 97 | import | +| r_regressions.py:97 | ControlFlowNode for _names | tuple object | builtin-class tuple | 95 | import | +| r_regressions.py:98 | ControlFlowNode for ImportExpr | Module time | builtin-class module | 98 | import | +| r_regressions.py:98 | ControlFlowNode for t | Module time | builtin-class module | 98 | import | +| s_scopes.py:4 | ControlFlowNode for True | bool True | builtin-class bool | 4 | import | +| s_scopes.py:4 | ControlFlowNode for float | bool True | builtin-class bool | 4 | import | +| s_scopes.py:7 | ControlFlowNode for C2 | class C2 | builtin-class type | 7 | import | +| s_scopes.py:7 | ControlFlowNode for ClassExpr | class C2 | builtin-class type | 7 | import | +| s_scopes.py:7 | ControlFlowNode for object | builtin-class object | builtin-class type | 7 | import | +| s_scopes.py:9 | ControlFlowNode for i1 | builtin-class int | builtin-class type | 9 | import | +| s_scopes.py:9 | ControlFlowNode for int | builtin-class int | builtin-class type | 9 | import | +| s_scopes.py:10 | ControlFlowNode for f1 | bool True | builtin-class bool | 4 | import | +| s_scopes.py:10 | ControlFlowNode for f1 | builtin-class float | builtin-class type | 10 | import | +| s_scopes.py:10 | ControlFlowNode for float | bool True | builtin-class bool | 4 | import | +| s_scopes.py:10 | ControlFlowNode for float | builtin-class float | builtin-class type | 10 | import | +| s_scopes.py:12 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 12 | import | +| s_scopes.py:12 | ControlFlowNode for int | int 0 | builtin-class int | 12 | import | +| s_scopes.py:15 | ControlFlowNode for FloatLiteral | float 1.0 | builtin-class float | 15 | import | +| s_scopes.py:15 | ControlFlowNode for str | float 1.0 | builtin-class float | 15 | import | +| s_scopes.py:17 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 17 | import | +| s_scopes.py:17 | ControlFlowNode for float | NoneType None | builtin-class NoneType | 17 | import | +| s_scopes.py:18 | ControlFlowNode for i2 | int 0 | builtin-class int | 12 | import | +| s_scopes.py:18 | ControlFlowNode for int | int 0 | builtin-class int | 12 | import | +| s_scopes.py:19 | ControlFlowNode for s | builtin-class str | builtin-class type | 19 | import | +| s_scopes.py:19 | ControlFlowNode for s | float 1.0 | builtin-class float | 15 | import | +| s_scopes.py:19 | ControlFlowNode for str | builtin-class str | builtin-class type | 19 | import | +| s_scopes.py:19 | ControlFlowNode for str | float 1.0 | builtin-class float | 15 | import | +| s_scopes.py:20 | ControlFlowNode for f2 | NoneType None | builtin-class NoneType | 17 | import | +| s_scopes.py:20 | ControlFlowNode for f2 | bool True | builtin-class bool | 4 | import | +| s_scopes.py:20 | ControlFlowNode for f2 | builtin-class float | builtin-class type | 20 | import | +| s_scopes.py:20 | ControlFlowNode for float | NoneType None | builtin-class NoneType | 17 | import | +| s_scopes.py:20 | ControlFlowNode for float | bool True | builtin-class bool | 4 | import | +| s_scopes.py:20 | ControlFlowNode for float | builtin-class float | builtin-class type | 20 | import | +| s_scopes.py:23 | ControlFlowNode for i | builtin-class int | builtin-class type | 23 | import | +| s_scopes.py:23 | ControlFlowNode for int | builtin-class int | builtin-class type | 23 | import | +| s_scopes.py:24 | ControlFlowNode for f | bool True | builtin-class bool | 4 | import | +| s_scopes.py:24 | ControlFlowNode for f | builtin-class float | builtin-class type | 24 | import | +| s_scopes.py:24 | ControlFlowNode for float | bool True | builtin-class bool | 4 | import | +| s_scopes.py:24 | ControlFlowNode for float | builtin-class float | builtin-class type | 24 | import | +| t_type.py:1 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | 1 | import | +| t_type.py:1 | ControlFlowNode for sys | Module sys | builtin-class module | 1 | import | +| t_type.py:3 | ControlFlowNode for C | class C | builtin-class type | 3 | import | +| t_type.py:3 | ControlFlowNode for ClassExpr | class C | builtin-class type | 3 | import | +| t_type.py:3 | ControlFlowNode for object | builtin-class object | builtin-class type | 3 | import | +| t_type.py:6 | ControlFlowNode for C | class C | builtin-class type | 3 | import | +| t_type.py:6 | ControlFlowNode for C() | C() | class C | 6 | import | +| t_type.py:6 | ControlFlowNode for type | builtin-class type | builtin-class type | 6 | import | +| t_type.py:6 | ControlFlowNode for type() | class C | builtin-class type | 3 | import | +| t_type.py:7 | ControlFlowNode for sys | Module sys | builtin-class module | 1 | import | +| t_type.py:7 | ControlFlowNode for type | builtin-class type | builtin-class type | 7 | import | +| t_type.py:7 | ControlFlowNode for type() | builtin-class module | builtin-class type | 7 | import | +| t_type.py:9 | ControlFlowNode for type | builtin-class type | builtin-class type | 9 | import | +| t_type.py:10 | ControlFlowNode for Dict | Dict | builtin-class dict | 10 | import | +| t_type.py:10 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 10 | import | +| t_type.py:10 | ControlFlowNode for object | builtin-class object | builtin-class type | 10 | import | +| t_type.py:10 | ControlFlowNode for type | builtin-class type | builtin-class type | 10 | import | +| t_type.py:12 | ControlFlowNode for FunctionExpr | Function k | builtin-class function | 12 | import | +| t_type.py:12 | ControlFlowNode for k | Function k | builtin-class function | 12 | import | +| t_type.py:13 | ControlFlowNode for C | class C | builtin-class type | 3 | runtime | +| t_type.py:13 | ControlFlowNode for C() | C() | class C | 13 | runtime | +| t_type.py:13 | ControlFlowNode for type | builtin-class type | builtin-class type | 13 | runtime | +| t_type.py:13 | ControlFlowNode for type() | class C | builtin-class type | 3 | runtime | +| t_type.py:14 | ControlFlowNode for sys | Module sys | builtin-class module | 1 | runtime | +| t_type.py:14 | ControlFlowNode for type | builtin-class type | builtin-class type | 14 | runtime | +| t_type.py:14 | ControlFlowNode for type() | builtin-class module | builtin-class type | 14 | runtime | +| t_type.py:15 | ControlFlowNode for type | builtin-class type | builtin-class type | 15 | runtime | +| t_type.py:15 | ControlFlowNode for type() | *UNKNOWN TYPE* | builtin-class type | 15 | runtime | +| t_type.py:16 | ControlFlowNode for Dict | Dict | builtin-class dict | 16 | runtime | +| t_type.py:16 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 16 | runtime | +| t_type.py:16 | ControlFlowNode for object | builtin-class object | builtin-class type | 16 | runtime | +| t_type.py:16 | ControlFlowNode for type | builtin-class type | builtin-class type | 16 | runtime | +| u_paired_values.py:2 | ControlFlowNode for FunctionExpr | Function return_if_true | builtin-class function | 2 | import | +| u_paired_values.py:2 | ControlFlowNode for return_if_true | Function return_if_true | builtin-class function | 2 | import | +| u_paired_values.py:3 | ControlFlowNode for cond | bool False | builtin-class bool | 8 | code/u_paired_values.py:8 from code/u_paired_values.py:14 from import | +| u_paired_values.py:3 | ControlFlowNode for cond | bool False | builtin-class bool | 8 | code/u_paired_values.py:8 from runtime | +| u_paired_values.py:3 | ControlFlowNode for cond | bool True | builtin-class bool | 8 | code/u_paired_values.py:8 from code/u_paired_values.py:11 from import | +| u_paired_values.py:3 | ControlFlowNode for cond | bool True | builtin-class bool | 8 | code/u_paired_values.py:8 from runtime | +| u_paired_values.py:4 | ControlFlowNode for val | int 1 | builtin-class int | 8 | code/u_paired_values.py:8 from code/u_paired_values.py:11 from import | +| u_paired_values.py:4 | ControlFlowNode for val | int 1 | builtin-class int | 8 | code/u_paired_values.py:8 from runtime | +| u_paired_values.py:5 | ControlFlowNode for Exception | builtin-class Exception | builtin-class type | 5 | code/u_paired_values.py:8 from code/u_paired_values.py:14 from import | +| u_paired_values.py:5 | ControlFlowNode for Exception | builtin-class Exception | builtin-class type | 5 | code/u_paired_values.py:8 from runtime | +| u_paired_values.py:5 | ControlFlowNode for Exception | builtin-class Exception | builtin-class type | 5 | runtime | +| u_paired_values.py:5 | ControlFlowNode for Exception() | Exception() | builtin-class Exception | 5 | code/u_paired_values.py:8 from code/u_paired_values.py:14 from import | +| u_paired_values.py:5 | ControlFlowNode for Exception() | Exception() | builtin-class Exception | 5 | code/u_paired_values.py:8 from runtime | +| u_paired_values.py:5 | ControlFlowNode for Exception() | Exception() | builtin-class Exception | 5 | runtime | +| u_paired_values.py:7 | ControlFlowNode for FunctionExpr | Function test | builtin-class function | 7 | import | +| u_paired_values.py:7 | ControlFlowNode for test | Function test | builtin-class function | 7 | import | +| u_paired_values.py:8 | ControlFlowNode for False | bool False | builtin-class bool | 8 | code/u_paired_values.py:14 from import | +| u_paired_values.py:8 | ControlFlowNode for False | bool False | builtin-class bool | 8 | runtime | +| u_paired_values.py:8 | ControlFlowNode for IfExp | int 1 | builtin-class int | 8 | code/u_paired_values.py:11 from import | +| u_paired_values.py:8 | ControlFlowNode for IfExp | int 1 | builtin-class int | 8 | runtime | +| u_paired_values.py:8 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 8 | code/u_paired_values.py:11 from import | +| u_paired_values.py:8 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 8 | runtime | +| u_paired_values.py:8 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | 8 | code/u_paired_values.py:14 from import | +| u_paired_values.py:8 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | 8 | runtime | +| u_paired_values.py:8 | ControlFlowNode for True | bool True | builtin-class bool | 8 | code/u_paired_values.py:11 from import | +| u_paired_values.py:8 | ControlFlowNode for True | bool True | builtin-class bool | 8 | runtime | +| u_paired_values.py:8 | ControlFlowNode for cond | bool False | builtin-class bool | 14 | code/u_paired_values.py:14 from import | +| u_paired_values.py:8 | ControlFlowNode for cond | bool True | builtin-class bool | 11 | code/u_paired_values.py:11 from import | +| u_paired_values.py:8 | ControlFlowNode for return_if_true | Function return_if_true | builtin-class function | 2 | code/u_paired_values.py:11 from import | +| u_paired_values.py:8 | ControlFlowNode for return_if_true | Function return_if_true | builtin-class function | 2 | code/u_paired_values.py:14 from import | +| u_paired_values.py:8 | ControlFlowNode for return_if_true | Function return_if_true | builtin-class function | 2 | runtime | +| u_paired_values.py:8 | ControlFlowNode for return_if_true() | int 1 | builtin-class int | 8 | code/u_paired_values.py:11 from import | +| u_paired_values.py:8 | ControlFlowNode for return_if_true() | int 1 | builtin-class int | 8 | runtime | +| u_paired_values.py:8 | ControlFlowNode for x | int 1 | builtin-class int | 8 | code/u_paired_values.py:11 from import | +| u_paired_values.py:8 | ControlFlowNode for x | int 1 | builtin-class int | 8 | runtime | +| u_paired_values.py:9 | ControlFlowNode for x | int 1 | builtin-class int | 8 | code/u_paired_values.py:11 from import | +| u_paired_values.py:9 | ControlFlowNode for x | int 1 | builtin-class int | 8 | runtime | +| u_paired_values.py:11 | ControlFlowNode for True | bool True | builtin-class bool | 11 | import | +| u_paired_values.py:11 | ControlFlowNode for test | Function test | builtin-class function | 7 | import | +| u_paired_values.py:11 | ControlFlowNode for test() | int 1 | builtin-class int | 8 | import | +| u_paired_values.py:11 | ControlFlowNode for y | int 1 | builtin-class int | 8 | import | +| u_paired_values.py:12 | ControlFlowNode for y | int 1 | builtin-class int | 8 | import | +| u_paired_values.py:14 | ControlFlowNode for False | bool False | builtin-class bool | 14 | import | +| u_paired_values.py:14 | ControlFlowNode for test | Function test | builtin-class function | 7 | import | diff --git a/python/ql/test/library-tests/PointsTo/new/PointsToWithContext.ql b/python/ql/test/library-tests/PointsTo/new/PointsToWithContext.ql new file mode 100755 index 000000000000..e2ef1fc3c61d --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/PointsToWithContext.ql @@ -0,0 +1,10 @@ +import python +import Util +import semmle.python.pointsto.PointsTo +import semmle.python.pointsto.PointsToContext + +from ControlFlowNode f, Object o, ClassObject c, ControlFlowNode x, PointsToContext ctx + +where PointsTo::points_to(f, ctx, o, c, x) + +select locate(f.getLocation(), "abeghijklmnpqrstu"), f.toString(), repr(o), repr(c), x.getLocation().getStartLine(), ctx diff --git a/python/ql/test/library-tests/PointsTo/new/PointsToWithType.expected b/python/ql/test/library-tests/PointsTo/new/PointsToWithType.expected new file mode 100644 index 000000000000..f3fc21e75b56 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/PointsToWithType.expected @@ -0,0 +1,712 @@ +| a_simple.py:2 | ControlFlowNode for FloatLiteral | float 1.0 | builtin-class float | 2 | +| a_simple.py:2 | ControlFlowNode for f1 | float 1.0 | builtin-class float | 2 | +| a_simple.py:3 | ControlFlowNode for dict | builtin-class dict | builtin-class type | 3 | +| a_simple.py:4 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type | 4 | +| a_simple.py:5 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 5 | +| a_simple.py:5 | ControlFlowNode for i1 | int 0 | builtin-class int | 5 | +| a_simple.py:6 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 6 | +| a_simple.py:6 | ControlFlowNode for s | Tuple | builtin-class tuple | 6 | +| a_simple.py:8 | ControlFlowNode for FunctionExpr | Function func | builtin-class function | 8 | +| a_simple.py:8 | ControlFlowNode for func | Function func | builtin-class function | 8 | +| a_simple.py:11 | ControlFlowNode for C | class C | builtin-class type | 11 | +| a_simple.py:11 | ControlFlowNode for ClassExpr | class C | builtin-class type | 11 | +| a_simple.py:11 | ControlFlowNode for object | builtin-class object | builtin-class type | 11 | +| a_simple.py:14 | ControlFlowNode for FunctionExpr | Function vararg_kwarg | builtin-class function | 14 | +| a_simple.py:14 | ControlFlowNode for d | d | builtin-class dict | 14 | +| a_simple.py:14 | ControlFlowNode for t | t | builtin-class tuple | 14 | +| a_simple.py:14 | ControlFlowNode for vararg_kwarg | Function vararg_kwarg | builtin-class function | 14 | +| a_simple.py:15 | ControlFlowNode for t | t | builtin-class tuple | 14 | +| a_simple.py:16 | ControlFlowNode for d | d | builtin-class dict | 14 | +| a_simple.py:18 | ControlFlowNode for FunctionExpr | Function multi_loop | builtin-class function | 18 | +| a_simple.py:18 | ControlFlowNode for multi_loop | Function multi_loop | builtin-class function | 18 | +| a_simple.py:19 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 19 | +| a_simple.py:19 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 19 | +| a_simple.py:20 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 20 | +| a_simple.py:23 | ControlFlowNode for FunctionExpr | Function with_definition | builtin-class function | 23 | +| a_simple.py:23 | ControlFlowNode for with_definition | Function with_definition | builtin-class function | 23 | +| a_simple.py:27 | ControlFlowNode for FunctionExpr | Function multi_loop_in_try | builtin-class function | 27 | +| a_simple.py:27 | ControlFlowNode for multi_loop_in_try | Function multi_loop_in_try | builtin-class function | 27 | +| a_simple.py:29 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 29 | +| a_simple.py:31 | ControlFlowNode for KeyError | builtin-class KeyError | builtin-class type | 31 | +| a_simple.py:34 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 34 | +| a_simple.py:34 | ControlFlowNode for args | args | builtin-class tuple | 34 | +| a_simple.py:34 | ControlFlowNode for f | Function f | builtin-class function | 34 | +| a_simple.py:34 | ControlFlowNode for kwargs | kwargs | builtin-class dict | 34 | +| a_simple.py:35 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 35 | +| a_simple.py:35 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 35 | +| a_simple.py:35 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 35 | +| a_simple.py:35 | ControlFlowNode for args | args | builtin-class tuple | 34 | +| a_simple.py:36 | ControlFlowNode for Str | 'x' | builtin-class str | 36 | +| a_simple.py:36 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 36 | +| a_simple.py:36 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 36 | +| a_simple.py:36 | ControlFlowNode for kwargs | kwargs | builtin-class dict | 34 | +| b_condition.py:4 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 4 | +| b_condition.py:4 | ControlFlowNode for f | Function f | builtin-class function | 4 | +| b_condition.py:5 | ControlFlowNode for IfExp | NoneType None | builtin-class NoneType | 5 | +| b_condition.py:5 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 5 | +| b_condition.py:5 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 5 | +| b_condition.py:7 | ControlFlowNode for Compare | bool False | builtin-class bool | 7 | +| b_condition.py:7 | ControlFlowNode for Compare | bool True | builtin-class bool | 7 | +| b_condition.py:7 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 7 | +| b_condition.py:7 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 5 | +| b_condition.py:8 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 8 | +| b_condition.py:8 | ControlFlowNode for x | int 7 | builtin-class int | 8 | +| b_condition.py:9 | ControlFlowNode for x | int 7 | builtin-class int | 8 | +| b_condition.py:11 | ControlFlowNode for IfExp | NoneType None | builtin-class NoneType | 11 | +| b_condition.py:11 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 11 | +| b_condition.py:11 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 11 | +| b_condition.py:13 | ControlFlowNode for Compare | bool False | builtin-class bool | 13 | +| b_condition.py:13 | ControlFlowNode for Compare | bool True | builtin-class bool | 13 | +| b_condition.py:13 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 13 | +| b_condition.py:13 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 11 | +| b_condition.py:14 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 14 | +| b_condition.py:14 | ControlFlowNode for x | int 7 | builtin-class int | 14 | +| b_condition.py:15 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 11 | +| b_condition.py:15 | ControlFlowNode for x | int 7 | builtin-class int | 14 | +| b_condition.py:17 | ControlFlowNode for IfExp | NoneType None | builtin-class NoneType | 17 | +| b_condition.py:17 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 17 | +| b_condition.py:17 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 17 | +| b_condition.py:19 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 19 | +| b_condition.py:19 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 19 | +| b_condition.py:19 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 17 | +| b_condition.py:20 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 20 | +| b_condition.py:20 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 20 | +| b_condition.py:21 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 20 | +| b_condition.py:23 | ControlFlowNode for IfExp | NoneType None | builtin-class NoneType | 23 | +| b_condition.py:23 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 23 | +| b_condition.py:23 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 23 | +| b_condition.py:25 | ControlFlowNode for IfExp | int 1 | builtin-class int | 25 | +| b_condition.py:25 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 25 | +| b_condition.py:25 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 23 | +| b_condition.py:25 | ControlFlowNode for x | int 1 | builtin-class int | 25 | +| b_condition.py:26 | ControlFlowNode for x | int 1 | builtin-class int | 25 | +| b_condition.py:28 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 28 | +| b_condition.py:28 | ControlFlowNode for x | int 1 | builtin-class int | 28 | +| b_condition.py:29 | ControlFlowNode for x | int 1 | builtin-class int | 25 | +| b_condition.py:29 | ControlFlowNode for x | int 1 | builtin-class int | 28 | +| b_condition.py:31 | ControlFlowNode for IfExp | int 1 | builtin-class int | 31 | +| b_condition.py:31 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 31 | +| b_condition.py:31 | ControlFlowNode for x | int 1 | builtin-class int | 31 | +| b_condition.py:32 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 32 | +| b_condition.py:32 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 32 | +| b_condition.py:32 | ControlFlowNode for x | int 1 | builtin-class int | 31 | +| b_condition.py:33 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 33 | +| b_condition.py:33 | ControlFlowNode for x | int 7 | builtin-class int | 33 | +| b_condition.py:34 | ControlFlowNode for x | int 1 | builtin-class int | 31 | +| b_condition.py:34 | ControlFlowNode for x | int 7 | builtin-class int | 33 | +| b_condition.py:36 | ControlFlowNode for int | builtin-class int | builtin-class type | 36 | +| b_condition.py:36 | ControlFlowNode for isinstance | Builtin-function isinstance | builtin-class builtin_function_or_method | 36 | +| b_condition.py:36 | ControlFlowNode for isinstance() | bool False | builtin-class bool | 36 | +| b_condition.py:36 | ControlFlowNode for isinstance() | bool True | builtin-class bool | 36 | +| b_condition.py:36 | ControlFlowNode for x | int 1 | builtin-class int | 31 | +| b_condition.py:36 | ControlFlowNode for x | int 7 | builtin-class int | 33 | +| b_condition.py:37 | ControlFlowNode for x | int 1 | builtin-class int | 31 | +| b_condition.py:37 | ControlFlowNode for x | int 7 | builtin-class int | 33 | +| b_condition.py:41 | ControlFlowNode for Attribute | int 1 | builtin-class int | 41 | +| b_condition.py:41 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 41 | +| b_condition.py:42 | ControlFlowNode for Compare | bool False | builtin-class bool | 42 | +| b_condition.py:42 | ControlFlowNode for Compare | bool True | builtin-class bool | 42 | +| b_condition.py:42 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 42 | +| b_condition.py:43 | ControlFlowNode for Attribute | int 1 | builtin-class int | 41 | +| b_condition.py:50 | ControlFlowNode for FunctionExpr | Function g | builtin-class function | 50 | +| b_condition.py:50 | ControlFlowNode for g | Function g | builtin-class function | 50 | +| b_condition.py:55 | ControlFlowNode for FunctionExpr | Function loop | builtin-class function | 55 | +| b_condition.py:55 | ControlFlowNode for loop | Function loop | builtin-class function | 55 | +| b_condition.py:61 | ControlFlowNode for FunctionExpr | Function double_attr_check | builtin-class function | 61 | +| b_condition.py:61 | ControlFlowNode for double_attr_check | Function double_attr_check | builtin-class function | 61 | +| b_condition.py:62 | ControlFlowNode for Compare | bool False | builtin-class bool | 62 | +| b_condition.py:62 | ControlFlowNode for Compare | bool True | builtin-class bool | 62 | +| b_condition.py:62 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | 62 | +| b_condition.py:65 | ControlFlowNode for Compare | bool False | builtin-class bool | 65 | +| b_condition.py:65 | ControlFlowNode for Compare | bool True | builtin-class bool | 65 | +| b_condition.py:65 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 65 | +| b_condition.py:66 | ControlFlowNode for Compare | bool False | builtin-class bool | 66 | +| b_condition.py:66 | ControlFlowNode for Compare | bool True | builtin-class bool | 66 | +| b_condition.py:69 | ControlFlowNode for FunctionExpr | Function h | builtin-class function | 69 | +| b_condition.py:69 | ControlFlowNode for h | Function h | builtin-class function | 69 | +| b_condition.py:70 | ControlFlowNode for IfExp | bool True | builtin-class bool | 70 | +| b_condition.py:70 | ControlFlowNode for True | bool True | builtin-class bool | 70 | +| b_condition.py:70 | ControlFlowNode for b | bool True | builtin-class bool | 70 | +| b_condition.py:71 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 71 | +| b_condition.py:71 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 71 | +| b_condition.py:71 | ControlFlowNode for b | bool True | builtin-class bool | 70 | +| b_condition.py:72 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 72 | +| b_condition.py:72 | ControlFlowNode for b | int 7 | builtin-class int | 72 | +| b_condition.py:73 | ControlFlowNode for b | bool True | builtin-class bool | 70 | +| b_condition.py:73 | ControlFlowNode for b | int 7 | builtin-class int | 72 | +| b_condition.py:75 | ControlFlowNode for FunctionExpr | Function k | builtin-class function | 75 | +| b_condition.py:75 | ControlFlowNode for k | Function k | builtin-class function | 75 | +| b_condition.py:76 | ControlFlowNode for t | builtin-class type | builtin-class type | 76 | +| b_condition.py:76 | ControlFlowNode for type | builtin-class type | builtin-class type | 76 | +| b_condition.py:77 | ControlFlowNode for Compare | bool True | builtin-class bool | 77 | +| b_condition.py:77 | ControlFlowNode for object | builtin-class object | builtin-class type | 77 | +| b_condition.py:77 | ControlFlowNode for t | builtin-class type | builtin-class type | 76 | +| b_condition.py:78 | ControlFlowNode for object | builtin-class object | builtin-class type | 78 | +| b_condition.py:78 | ControlFlowNode for t | builtin-class object | builtin-class type | 78 | +| b_condition.py:79 | ControlFlowNode for t | builtin-class object | builtin-class type | 78 | +| b_condition.py:81 | ControlFlowNode for FunctionExpr | Function odasa6261 | builtin-class function | 81 | +| b_condition.py:81 | ControlFlowNode for True | bool True | builtin-class bool | 81 | +| b_condition.py:81 | ControlFlowNode for odasa6261 | Function odasa6261 | builtin-class function | 81 | +| b_condition.py:82 | ControlFlowNode for callable | Builtin-function callable | builtin-class builtin_function_or_method | 82 | +| b_condition.py:82 | ControlFlowNode for callable() | bool False | builtin-class bool | 82 | +| b_condition.py:82 | ControlFlowNode for callable() | bool True | builtin-class bool | 82 | +| b_condition.py:82 | ControlFlowNode for foo | bool True | builtin-class bool | 81 | +| b_condition.py:83 | ControlFlowNode for FunctionExpr | Function bar | builtin-class function | 83 | +| b_condition.py:83 | ControlFlowNode for bar | Function bar | builtin-class function | 83 | +| b_condition.py:87 | ControlFlowNode for FunctionExpr | Function split_bool1 | builtin-class function | 87 | +| b_condition.py:87 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 87 | +| b_condition.py:87 | ControlFlowNode for split_bool1 | Function split_bool1 | builtin-class function | 87 | +| b_condition.py:88 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 87 | +| b_condition.py:88 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 87 | +| b_condition.py:90 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 87 | +| b_condition.py:90 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 87 | +| b_condition.py:92 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 87 | +| b_condition.py:93 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 87 | +| b_condition.py:96 | ControlFlowNode for y | NoneType None | builtin-class NoneType | 87 | +| b_condition.py:97 | ControlFlowNode for x | NoneType None | builtin-class NoneType | 87 | +| b_condition.py:101 | ControlFlowNode for FunctionExpr | Function not_or_not | builtin-class function | 101 | +| b_condition.py:101 | ControlFlowNode for a | a | builtin-class tuple | 101 | +| b_condition.py:101 | ControlFlowNode for not_or_not | Function not_or_not | builtin-class function | 101 | +| b_condition.py:102 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 102 | +| b_condition.py:102 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 102 | +| b_condition.py:102 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 102 | +| b_condition.py:102 | ControlFlowNode for a | a | builtin-class tuple | 101 | +| b_condition.py:102 | ControlFlowNode for isinstance | Builtin-function isinstance | builtin-class builtin_function_or_method | 102 | +| b_condition.py:102 | ControlFlowNode for isinstance() | bool False | builtin-class bool | 102 | +| b_condition.py:102 | ControlFlowNode for isinstance() | bool True | builtin-class bool | 102 | +| b_condition.py:102 | ControlFlowNode for list | builtin-class list | builtin-class type | 102 | +| b_condition.py:102 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type | 102 | +| b_condition.py:103 | ControlFlowNode for TypeError | builtin-class TypeError | builtin-class type | 103 | +| b_condition.py:103 | ControlFlowNode for TypeError() | TypeError() | builtin-class TypeError | 103 | +| b_condition.py:104 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 104 | +| b_condition.py:104 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 104 | +| b_condition.py:104 | ControlFlowNode for a | a | builtin-class tuple | 101 | +| b_condition.py:105 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 105 | +| b_condition.py:105 | ControlFlowNode for UnaryExpr | bool False | builtin-class bool | 105 | +| b_condition.py:105 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 105 | +| b_condition.py:105 | ControlFlowNode for a | a | builtin-class tuple | 101 | +| b_condition.py:106 | ControlFlowNode for Exception | builtin-class Exception | builtin-class type | 106 | +| b_condition.py:106 | ControlFlowNode for Exception() | Exception() | builtin-class Exception | 106 | +| b_condition.py:107 | ControlFlowNode for Str | 'Hello' | builtin-class str | 107 | +| d_globals.py:2 | ControlFlowNode for FunctionExpr | Function j | builtin-class function | 2 | +| d_globals.py:2 | ControlFlowNode for j | Function j | builtin-class function | 2 | +| d_globals.py:3 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 3 | +| d_globals.py:3 | ControlFlowNode for dict | int 7 | builtin-class int | 5 | +| d_globals.py:3 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type | 7 | +| d_globals.py:4 | ControlFlowNode for dict | builtin-class dict | builtin-class type | 4 | +| d_globals.py:5 | ControlFlowNode for IntegerLiteral | int 7 | builtin-class int | 5 | +| d_globals.py:5 | ControlFlowNode for dict | int 7 | builtin-class int | 5 | +| d_globals.py:6 | ControlFlowNode for dict | int 7 | builtin-class int | 5 | +| d_globals.py:7 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type | 7 | +| d_globals.py:8 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type | 7 | +| d_globals.py:14 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 14 | +| d_globals.py:14 | ControlFlowNode for g1 | NoneType None | builtin-class NoneType | 14 | +| d_globals.py:16 | ControlFlowNode for FunctionExpr | Function assign_global | builtin-class function | 16 | +| d_globals.py:16 | ControlFlowNode for assign_global | Function assign_global | builtin-class function | 16 | +| d_globals.py:18 | ControlFlowNode for IntegerLiteral | int 101 | builtin-class int | 18 | +| d_globals.py:18 | ControlFlowNode for g1 | int 101 | builtin-class int | 18 | +| d_globals.py:19 | ControlFlowNode for g1 | int 101 | builtin-class int | 18 | +| d_globals.py:23 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 23 | +| d_globals.py:23 | ControlFlowNode for g2 | NoneType None | builtin-class NoneType | 23 | +| d_globals.py:25 | ControlFlowNode for FunctionExpr | Function init | builtin-class function | 25 | +| d_globals.py:25 | ControlFlowNode for init | Function init | builtin-class function | 25 | +| d_globals.py:27 | ControlFlowNode for IntegerLiteral | int 102 | builtin-class int | 27 | +| d_globals.py:27 | ControlFlowNode for g2 | int 102 | builtin-class int | 27 | +| d_globals.py:29 | ControlFlowNode for init | Function init | builtin-class function | 25 | +| d_globals.py:29 | ControlFlowNode for init() | NoneType None | builtin-class NoneType | 25 | +| d_globals.py:30 | ControlFlowNode for g2 | int 102 | builtin-class int | 27 | +| d_globals.py:33 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 33 | +| d_globals.py:33 | ControlFlowNode for g3 | NoneType None | builtin-class NoneType | 33 | +| d_globals.py:35 | ControlFlowNode for ClassExpr | class Ugly | builtin-class type | 35 | +| d_globals.py:35 | ControlFlowNode for Ugly | class Ugly | builtin-class type | 35 | +| d_globals.py:35 | ControlFlowNode for object | builtin-class object | builtin-class type | 35 | +| d_globals.py:37 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 37 | +| d_globals.py:37 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 37 | +| d_globals.py:39 | ControlFlowNode for IntegerLiteral | int 103 | builtin-class int | 39 | +| d_globals.py:39 | ControlFlowNode for g3 | int 103 | builtin-class int | 39 | +| d_globals.py:41 | ControlFlowNode for FunctionExpr | Function meth | builtin-class function | 41 | +| d_globals.py:41 | ControlFlowNode for meth | Function meth | builtin-class function | 41 | +| d_globals.py:42 | ControlFlowNode for g3 | int 103 | builtin-class int | 39 | +| d_globals.py:45 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 45 | +| d_globals.py:45 | ControlFlowNode for x | int 0 | builtin-class int | 45 | +| d_globals.py:46 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 46 | +| d_globals.py:46 | ControlFlowNode for x | int 1 | builtin-class int | 46 | +| d_globals.py:47 | ControlFlowNode for x | int 1 | builtin-class int | 46 | +| d_globals.py:49 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | 49 | +| d_globals.py:49 | ControlFlowNode for x | int 3 | builtin-class int | 49 | +| d_globals.py:52 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 52 | +| d_globals.py:52 | ControlFlowNode for y | int 1 | builtin-class int | 52 | +| d_globals.py:54 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | 54 | +| d_globals.py:54 | ControlFlowNode for y | int 2 | builtin-class int | 54 | +| d_globals.py:59 | ControlFlowNode for y | int 1 | builtin-class int | 52 | +| d_globals.py:59 | ControlFlowNode for y | int 2 | builtin-class int | 54 | +| d_globals.py:62 | ControlFlowNode for ClassExpr | class X | builtin-class type | 62 | +| d_globals.py:62 | ControlFlowNode for X | class X | builtin-class type | 62 | +| d_globals.py:62 | ControlFlowNode for object | builtin-class object | builtin-class type | 62 | +| d_globals.py:63 | ControlFlowNode for y | int 1 | builtin-class int | 52 | +| d_globals.py:63 | ControlFlowNode for y | int 2 | builtin-class int | 54 | +| d_globals.py:66 | ControlFlowNode for g3 | NoneType None | builtin-class NoneType | 33 | +| d_globals.py:68 | ControlFlowNode for type | builtin-class type | builtin-class type | 68 | +| d_globals.py:70 | ControlFlowNode for FunctionExpr | Function k | builtin-class function | 70 | +| d_globals.py:70 | ControlFlowNode for k | Function k | builtin-class function | 70 | +| d_globals.py:71 | ControlFlowNode for type | builtin-class type | builtin-class type | 71 | +| d_globals.py:73 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 73 | +| d_globals.py:73 | ControlFlowNode for g4 | NoneType None | builtin-class NoneType | 73 | +| d_globals.py:75 | ControlFlowNode for FunctionExpr | Function get_g4 | builtin-class function | 75 | +| d_globals.py:75 | ControlFlowNode for get_g4 | Function get_g4 | builtin-class function | 75 | +| d_globals.py:76 | ControlFlowNode for UnaryExpr | bool True | builtin-class bool | 76 | +| d_globals.py:76 | ControlFlowNode for g4 | NoneType None | builtin-class NoneType | 73 | +| d_globals.py:77 | ControlFlowNode for set_g4 | Function set_g4 | builtin-class function | 80 | +| d_globals.py:77 | ControlFlowNode for set_g4() | NoneType None | builtin-class NoneType | 80 | +| d_globals.py:78 | ControlFlowNode for g4 | bool False | builtin-class bool | 85 | +| d_globals.py:80 | ControlFlowNode for FunctionExpr | Function set_g4 | builtin-class function | 80 | +| d_globals.py:80 | ControlFlowNode for set_g4 | Function set_g4 | builtin-class function | 80 | +| d_globals.py:81 | ControlFlowNode for set_g4_indirect | Function set_g4_indirect | builtin-class function | 83 | +| d_globals.py:81 | ControlFlowNode for set_g4_indirect() | NoneType None | builtin-class NoneType | 83 | +| d_globals.py:83 | ControlFlowNode for FunctionExpr | Function set_g4_indirect | builtin-class function | 83 | +| d_globals.py:83 | ControlFlowNode for set_g4_indirect | Function set_g4_indirect | builtin-class function | 83 | +| d_globals.py:85 | ControlFlowNode for False | bool False | builtin-class bool | 85 | +| d_globals.py:85 | ControlFlowNode for g4 | bool False | builtin-class bool | 85 | +| d_globals.py:87 | ControlFlowNode for ClassExpr | class modinit | builtin-class type | 87 | +| d_globals.py:87 | ControlFlowNode for modinit | class modinit | builtin-class type | 87 | +| d_globals.py:87 | ControlFlowNode for object | builtin-class object | builtin-class type | 87 | +| d_globals.py:90 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 90 | +| d_globals.py:90 | ControlFlowNode for z | int 0 | builtin-class int | 90 | +| d_globals.py:95 | ControlFlowNode for FunctionExpr | Function outer | builtin-class function | 95 | +| d_globals.py:95 | ControlFlowNode for outer | Function outer | builtin-class function | 95 | +| d_globals.py:96 | ControlFlowNode for FunctionExpr | Function inner | builtin-class function | 96 | +| d_globals.py:96 | ControlFlowNode for inner | Function inner | builtin-class function | 96 | +| d_globals.py:98 | ControlFlowNode for IntegerLiteral | int 100 | builtin-class int | 98 | +| d_globals.py:98 | ControlFlowNode for glob | int 100 | builtin-class int | 98 | +| d_globals.py:99 | ControlFlowNode for glob | int 100 | builtin-class int | 98 | +| d_globals.py:101 | ControlFlowNode for FunctionExpr | Function otherInner | builtin-class function | 101 | +| d_globals.py:101 | ControlFlowNode for otherInner | Function otherInner | builtin-class function | 101 | +| d_globals.py:104 | ControlFlowNode for inner | Function inner | builtin-class function | 96 | +| d_globals.py:104 | ControlFlowNode for inner() | int 100 | builtin-class int | 98 | +| d_globals.py:107 | ControlFlowNode for FunctionExpr | Function redefine | builtin-class function | 107 | +| d_globals.py:107 | ControlFlowNode for redefine | Function redefine | builtin-class function | 107 | +| d_globals.py:110 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 110 | +| d_globals.py:110 | ControlFlowNode for z | int 1 | builtin-class int | 110 | +| d_globals.py:111 | ControlFlowNode for z | int 1 | builtin-class int | 110 | +| d_globals.py:113 | ControlFlowNode for IntegerLiteral | int 50 | builtin-class int | 113 | +| d_globals.py:113 | ControlFlowNode for glob | int 50 | builtin-class int | 113 | +| d_globals.py:114 | ControlFlowNode for glob | int 50 | builtin-class int | 113 | +| d_globals.py:118 | ControlFlowNode for ClassExpr | class D | builtin-class type | 118 | +| d_globals.py:118 | ControlFlowNode for D | class D | builtin-class type | 118 | +| d_globals.py:118 | ControlFlowNode for object | builtin-class object | builtin-class type | 118 | +| d_globals.py:120 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 120 | +| d_globals.py:120 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 120 | +| d_globals.py:123 | ControlFlowNode for FunctionExpr | Function foo | builtin-class function | 123 | +| d_globals.py:123 | ControlFlowNode for foo | Function foo | builtin-class function | 123 | +| d_globals.py:124 | ControlFlowNode for dict | int 7 | builtin-class int | 5 | +| d_globals.py:126 | ControlFlowNode for FunctionExpr | Function use_list_attribute | builtin-class function | 126 | +| d_globals.py:126 | ControlFlowNode for use_list_attribute | Function use_list_attribute | builtin-class function | 126 | +| d_globals.py:127 | ControlFlowNode for List | List | builtin-class list | 127 | +| d_globals.py:127 | ControlFlowNode for l | List | builtin-class list | 127 | +| d_globals.py:128 | ControlFlowNode for Attribute | Builtin-method append | builtin-class method_descriptor | 128 | +| d_globals.py:128 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 128 | +| d_globals.py:128 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 128 | +| d_globals.py:128 | ControlFlowNode for l | List | builtin-class list | 127 | +| d_globals.py:128 | ControlFlowNode for list | builtin-class list | builtin-class type | 128 | +| d_globals.py:129 | ControlFlowNode for l | List | builtin-class list | 127 | +| e_temporal.py:2 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | 2 | +| e_temporal.py:2 | ControlFlowNode for sys | Module sys | builtin-class module | 2 | +| e_temporal.py:4 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 4 | +| e_temporal.py:4 | ControlFlowNode for f | Function f | builtin-class function | 4 | +| e_temporal.py:5 | ControlFlowNode for Attribute | list object | builtin-class list | 5 | +| e_temporal.py:5 | ControlFlowNode for Compare | bool False | builtin-class bool | 5 | +| e_temporal.py:5 | ControlFlowNode for Compare | bool True | builtin-class bool | 5 | +| e_temporal.py:5 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | 5 | +| e_temporal.py:5 | ControlFlowNode for len | Builtin-function len | builtin-class builtin_function_or_method | 5 | +| e_temporal.py:5 | ControlFlowNode for len() | len() | builtin-class int | 5 | +| e_temporal.py:5 | ControlFlowNode for sys | Module sys | builtin-class module | 2 | +| e_temporal.py:7 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 7 | +| e_temporal.py:9 | ControlFlowNode for FunctionExpr | Function g | builtin-class function | 9 | +| e_temporal.py:9 | ControlFlowNode for g | Function g | builtin-class function | 9 | +| e_temporal.py:10 | ControlFlowNode for arg | int 1 | builtin-class int | 7 | +| e_temporal.py:12 | ControlFlowNode for f | Function f | builtin-class function | 4 | +| e_temporal.py:12 | ControlFlowNode for f() | int 1 | builtin-class int | 7 | +| e_temporal.py:12 | ControlFlowNode for g | Function g | builtin-class function | 9 | +| e_temporal.py:12 | ControlFlowNode for g() | int 1 | builtin-class int | 7 | +| e_temporal.py:12 | ControlFlowNode for x | int 1 | builtin-class int | 7 | +| g_class_init.py:3 | ControlFlowNode for C | class C | builtin-class type | 3 | +| g_class_init.py:3 | ControlFlowNode for ClassExpr | class C | builtin-class type | 3 | +| g_class_init.py:3 | ControlFlowNode for object | builtin-class object | builtin-class type | 3 | +| g_class_init.py:5 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 5 | +| g_class_init.py:5 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 5 | +| g_class_init.py:6 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 9 | +| g_class_init.py:6 | ControlFlowNode for self | self | class C | 5 | +| g_class_init.py:7 | ControlFlowNode for Attribute | int 1 | builtin-class int | 7 | +| g_class_init.py:7 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 7 | +| g_class_init.py:7 | ControlFlowNode for self | self | class C | 5 | +| g_class_init.py:9 | ControlFlowNode for FunctionExpr | Function _init | builtin-class function | 9 | +| g_class_init.py:9 | ControlFlowNode for _init | Function _init | builtin-class function | 9 | +| g_class_init.py:10 | ControlFlowNode for Attribute | int 2 | builtin-class int | 10 | +| g_class_init.py:10 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | 10 | +| g_class_init.py:10 | ControlFlowNode for self | self | class C | 5 | +| g_class_init.py:11 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 13 | +| g_class_init.py:11 | ControlFlowNode for self | self | class C | 5 | +| g_class_init.py:13 | ControlFlowNode for FunctionExpr | Function _init2 | builtin-class function | 13 | +| g_class_init.py:13 | ControlFlowNode for _init2 | Function _init2 | builtin-class function | 13 | +| g_class_init.py:14 | ControlFlowNode for Attribute | int 3 | builtin-class int | 14 | +| g_class_init.py:14 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | 14 | +| g_class_init.py:14 | ControlFlowNode for self | self | class C | 5 | +| g_class_init.py:16 | ControlFlowNode for FunctionExpr | Function method | builtin-class function | 16 | +| g_class_init.py:16 | ControlFlowNode for method | Function method | builtin-class function | 16 | +| g_class_init.py:17 | ControlFlowNode for Attribute | int 1 | builtin-class int | 7 | +| g_class_init.py:17 | ControlFlowNode for self | self | class C | 16 | +| g_class_init.py:18 | ControlFlowNode for Attribute | int 2 | builtin-class int | 10 | +| g_class_init.py:18 | ControlFlowNode for int | builtin-class int | builtin-class type | 18 | +| g_class_init.py:18 | ControlFlowNode for isinstance | Builtin-function isinstance | builtin-class builtin_function_or_method | 18 | +| g_class_init.py:18 | ControlFlowNode for isinstance() | bool True | builtin-class bool | 18 | +| g_class_init.py:18 | ControlFlowNode for self | self | class C | 16 | +| g_class_init.py:19 | ControlFlowNode for Attribute | int 2 | builtin-class int | 10 | +| g_class_init.py:19 | ControlFlowNode for self | self | class C | 16 | +| g_class_init.py:20 | ControlFlowNode for Attribute | int 3 | builtin-class int | 14 | +| g_class_init.py:20 | ControlFlowNode for self | self | class C | 16 | +| g_class_init.py:24 | ControlFlowNode for ClassExpr | class Oddities | builtin-class type | 24 | +| g_class_init.py:24 | ControlFlowNode for Oddities | class Oddities | builtin-class type | 24 | +| g_class_init.py:24 | ControlFlowNode for object | builtin-class object | builtin-class type | 24 | +| g_class_init.py:26 | ControlFlowNode for int | builtin-class int | builtin-class type | 26 | +| g_class_init.py:27 | ControlFlowNode for float | builtin-class float | builtin-class type | 27 | +| g_class_init.py:28 | ControlFlowNode for l | Builtin-function len | builtin-class builtin_function_or_method | 28 | +| g_class_init.py:28 | ControlFlowNode for len | Builtin-function len | builtin-class builtin_function_or_method | 28 | +| g_class_init.py:29 | ControlFlowNode for h | Builtin-function hash | builtin-class builtin_function_or_method | 29 | +| g_class_init.py:29 | ControlFlowNode for hash | Builtin-function hash | builtin-class builtin_function_or_method | 29 | +| g_class_init.py:32 | ControlFlowNode for ClassExpr | class D | builtin-class type | 32 | +| g_class_init.py:32 | ControlFlowNode for D | class D | builtin-class type | 32 | +| g_class_init.py:32 | ControlFlowNode for object | builtin-class object | builtin-class type | 32 | +| g_class_init.py:34 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 34 | +| g_class_init.py:34 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 34 | +| g_class_init.py:35 | ControlFlowNode for Attribute | super().x | builtin-class method | 35 | +| g_class_init.py:35 | ControlFlowNode for D | class D | builtin-class type | 32 | +| g_class_init.py:35 | ControlFlowNode for self | self | class D | 34 | +| g_class_init.py:35 | ControlFlowNode for super | builtin-class super | builtin-class type | 35 | +| g_class_init.py:35 | ControlFlowNode for super() | super() | builtin-class super | 35 | +| g_class_init.py:36 | ControlFlowNode for Attribute | super().__init__ | builtin-class method | 36 | +| g_class_init.py:36 | ControlFlowNode for D | class D | builtin-class type | 32 | +| g_class_init.py:36 | ControlFlowNode for self | self | class D | 34 | +| g_class_init.py:36 | ControlFlowNode for super | builtin-class super | builtin-class type | 36 | +| g_class_init.py:36 | ControlFlowNode for super() | super() | builtin-class super | 36 | +| g_class_init.py:42 | ControlFlowNode for Str | 'v2' | builtin-class str | 42 | +| g_class_init.py:42 | ControlFlowNode for V2 | 'v2' | builtin-class str | 42 | +| g_class_init.py:43 | ControlFlowNode for Str | 'v3' | builtin-class str | 43 | +| g_class_init.py:43 | ControlFlowNode for V3 | 'v3' | builtin-class str | 43 | +| g_class_init.py:45 | ControlFlowNode for ClassExpr | class E | builtin-class type | 45 | +| g_class_init.py:45 | ControlFlowNode for E | class E | builtin-class type | 45 | +| g_class_init.py:45 | ControlFlowNode for object | builtin-class object | builtin-class type | 45 | +| g_class_init.py:46 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 46 | +| g_class_init.py:46 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 46 | +| g_class_init.py:48 | ControlFlowNode for Attribute | 'v2' | builtin-class str | 42 | +| g_class_init.py:48 | ControlFlowNode for V2 | 'v2' | builtin-class str | 42 | +| g_class_init.py:48 | ControlFlowNode for self | self | class E | 46 | +| g_class_init.py:50 | ControlFlowNode for Attribute | 'v3' | builtin-class str | 43 | +| g_class_init.py:50 | ControlFlowNode for V3 | 'v3' | builtin-class str | 43 | +| g_class_init.py:50 | ControlFlowNode for self | self | class E | 46 | +| g_class_init.py:52 | ControlFlowNode for FunctionExpr | Function meth | builtin-class function | 52 | +| g_class_init.py:52 | ControlFlowNode for meth | Function meth | builtin-class function | 52 | +| g_class_init.py:53 | ControlFlowNode for Attribute | 'v2' | builtin-class str | 42 | +| g_class_init.py:53 | ControlFlowNode for Attribute | 'v3' | builtin-class str | 43 | +| g_class_init.py:53 | ControlFlowNode for Compare | bool False | builtin-class bool | 53 | +| g_class_init.py:53 | ControlFlowNode for Compare | bool True | builtin-class bool | 53 | +| g_class_init.py:53 | ControlFlowNode for V2 | 'v2' | builtin-class str | 42 | +| g_class_init.py:53 | ControlFlowNode for self | self | class E | 52 | +| h_classes.py:1 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | 1 | +| h_classes.py:1 | ControlFlowNode for sys | Module sys | builtin-class module | 1 | +| h_classes.py:3 | ControlFlowNode for C | class C | builtin-class type | 3 | +| h_classes.py:3 | ControlFlowNode for ClassExpr | class C | builtin-class type | 3 | +| h_classes.py:3 | ControlFlowNode for object | builtin-class object | builtin-class type | 3 | +| h_classes.py:5 | ControlFlowNode for Str | 'C_x' | builtin-class str | 5 | +| h_classes.py:5 | ControlFlowNode for x | 'C_x' | builtin-class str | 5 | +| h_classes.py:7 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 7 | +| h_classes.py:7 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 7 | +| h_classes.py:8 | ControlFlowNode for Attribute | 'c_y' | builtin-class str | 8 | +| h_classes.py:8 | ControlFlowNode for Str | 'c_y' | builtin-class str | 8 | +| h_classes.py:8 | ControlFlowNode for self | self | class C | 7 | +| h_classes.py:10 | ControlFlowNode for C | class C | builtin-class type | 3 | +| h_classes.py:10 | ControlFlowNode for C() | C() | class C | 10 | +| h_classes.py:10 | ControlFlowNode for type | builtin-class type | builtin-class type | 10 | +| h_classes.py:10 | ControlFlowNode for type() | class C | builtin-class type | 3 | +| h_classes.py:11 | ControlFlowNode for sys | Module sys | builtin-class module | 1 | +| h_classes.py:11 | ControlFlowNode for type | builtin-class type | builtin-class type | 11 | +| h_classes.py:11 | ControlFlowNode for type() | builtin-class module | builtin-class type | 11 | +| h_classes.py:12 | ControlFlowNode for Dict | Dict | builtin-class dict | 12 | +| h_classes.py:12 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 12 | +| h_classes.py:12 | ControlFlowNode for object | builtin-class object | builtin-class type | 12 | +| h_classes.py:12 | ControlFlowNode for type | builtin-class type | builtin-class type | 12 | +| h_classes.py:14 | ControlFlowNode for FunctionExpr | Function k | builtin-class function | 14 | +| h_classes.py:14 | ControlFlowNode for k | Function k | builtin-class function | 14 | +| h_classes.py:15 | ControlFlowNode for C | class C | builtin-class type | 3 | +| h_classes.py:15 | ControlFlowNode for C() | C() | class C | 15 | +| h_classes.py:15 | ControlFlowNode for type | builtin-class type | builtin-class type | 15 | +| h_classes.py:15 | ControlFlowNode for type() | class C | builtin-class type | 3 | +| h_classes.py:16 | ControlFlowNode for sys | Module sys | builtin-class module | 1 | +| h_classes.py:16 | ControlFlowNode for type | builtin-class type | builtin-class type | 16 | +| h_classes.py:16 | ControlFlowNode for type() | builtin-class module | builtin-class type | 16 | +| h_classes.py:17 | ControlFlowNode for type | builtin-class type | builtin-class type | 17 | +| h_classes.py:17 | ControlFlowNode for type() | *UNKNOWN TYPE* | builtin-class type | 17 | +| h_classes.py:18 | ControlFlowNode for Dict | Dict | builtin-class dict | 18 | +| h_classes.py:18 | ControlFlowNode for Tuple | Tuple | builtin-class tuple | 18 | +| h_classes.py:18 | ControlFlowNode for object | builtin-class object | builtin-class type | 18 | +| h_classes.py:18 | ControlFlowNode for type | builtin-class type | builtin-class type | 18 | +| h_classes.py:23 | ControlFlowNode for Base | class Base | builtin-class type | 23 | +| h_classes.py:23 | ControlFlowNode for ClassExpr | class Base | builtin-class type | 23 | +| h_classes.py:23 | ControlFlowNode for object | builtin-class object | builtin-class type | 23 | +| h_classes.py:25 | ControlFlowNode for FunctionExpr | Function __init__ | builtin-class function | 25 | +| h_classes.py:25 | ControlFlowNode for __init__ | Function __init__ | builtin-class function | 25 | +| h_classes.py:26 | ControlFlowNode for Compare | bool False | builtin-class bool | 26 | +| h_classes.py:26 | ControlFlowNode for Compare | bool True | builtin-class bool | 26 | +| h_classes.py:26 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 26 | +| h_classes.py:27 | ControlFlowNode for Attribute | class Derived1 | builtin-class type | 33 | +| h_classes.py:27 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | 33 | +| h_classes.py:27 | ControlFlowNode for self | self | class Base | 25 | +| h_classes.py:28 | ControlFlowNode for Compare | bool False | builtin-class bool | 28 | +| h_classes.py:28 | ControlFlowNode for Compare | bool True | builtin-class bool | 28 | +| h_classes.py:28 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | 28 | +| h_classes.py:29 | ControlFlowNode for Attribute | class Derived2 | builtin-class type | 36 | +| h_classes.py:29 | ControlFlowNode for Derived2 | class Derived2 | builtin-class type | 36 | +| h_classes.py:29 | ControlFlowNode for self | self | class Base | 25 | +| h_classes.py:31 | ControlFlowNode for Attribute | class Derived3 | builtin-class type | 39 | +| h_classes.py:31 | ControlFlowNode for Derived3 | class Derived3 | builtin-class type | 39 | +| h_classes.py:31 | ControlFlowNode for self | self | class Base | 25 | +| h_classes.py:33 | ControlFlowNode for Base | class Base | builtin-class type | 23 | +| h_classes.py:33 | ControlFlowNode for ClassExpr | class Derived1 | builtin-class type | 33 | +| h_classes.py:33 | ControlFlowNode for Derived1 | class Derived1 | builtin-class type | 33 | +| h_classes.py:36 | ControlFlowNode for Base | class Base | builtin-class type | 23 | +| h_classes.py:36 | ControlFlowNode for ClassExpr | class Derived2 | builtin-class type | 36 | +| h_classes.py:36 | ControlFlowNode for Derived2 | class Derived2 | builtin-class type | 36 | +| h_classes.py:39 | ControlFlowNode for Base | class Base | builtin-class type | 23 | +| h_classes.py:39 | ControlFlowNode for ClassExpr | class Derived3 | builtin-class type | 39 | +| h_classes.py:39 | ControlFlowNode for Derived3 | class Derived3 | builtin-class type | 39 | +| h_classes.py:42 | ControlFlowNode for Base | class Base | builtin-class type | 23 | +| h_classes.py:45 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 45 | +| h_classes.py:45 | ControlFlowNode for f | Function f | builtin-class function | 45 | +| h_classes.py:48 | ControlFlowNode for ClassExpr | class D | builtin-class type | 48 | +| h_classes.py:48 | ControlFlowNode for D | class D | builtin-class type | 48 | +| h_classes.py:48 | ControlFlowNode for object | builtin-class object | builtin-class type | 48 | +| h_classes.py:50 | ControlFlowNode for f | Function f | builtin-class function | 45 | +| h_classes.py:50 | ControlFlowNode for m | Function f | builtin-class function | 45 | +| h_classes.py:52 | ControlFlowNode for FunctionExpr | Function n | builtin-class function | 52 | +| h_classes.py:52 | ControlFlowNode for n | Function n | builtin-class function | 52 | +| i_imports.py:3 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 3 | +| i_imports.py:3 | ControlFlowNode for a | int 1 | builtin-class int | 3 | +| i_imports.py:4 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | 4 | +| i_imports.py:4 | ControlFlowNode for b | int 2 | builtin-class int | 4 | +| i_imports.py:5 | ControlFlowNode for IntegerLiteral | int 3 | builtin-class int | 5 | +| i_imports.py:5 | ControlFlowNode for c | int 3 | builtin-class int | 5 | +| i_imports.py:7 | ControlFlowNode for ImportExpr | Module code.xyz | builtin-class module | 7 | +| i_imports.py:8 | ControlFlowNode for ImportExpr | Module code | builtin-class module | 8 | +| i_imports.py:8 | ControlFlowNode for ImportMember | Module code.xyz | builtin-class module | 0 | +| i_imports.py:8 | ControlFlowNode for xyz | Module code.xyz | builtin-class module | 0 | +| i_imports.py:9 | ControlFlowNode for Attribute | float 1.0 | builtin-class float | 2 | +| i_imports.py:9 | ControlFlowNode for xyz | Module code.xyz | builtin-class module | 0 | +| i_imports.py:10 | ControlFlowNode for z | float 3.0 | builtin-class float | 4 | +| i_imports.py:11 | ControlFlowNode for a | int 1 | builtin-class int | 3 | +| i_imports.py:13 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | 13 | +| i_imports.py:13 | ControlFlowNode for ImportMember | list object | builtin-class list | 13 | +| i_imports.py:13 | ControlFlowNode for argv | list object | builtin-class list | 13 | +| i_imports.py:15 | ControlFlowNode for argv | list object | builtin-class list | 13 | +| i_imports.py:17 | ControlFlowNode for ImportExpr | Module sys | builtin-class module | 17 | +| i_imports.py:17 | ControlFlowNode for sys | Module sys | builtin-class module | 17 | +| i_imports.py:18 | ControlFlowNode for Attribute | list object | builtin-class list | 18 | +| i_imports.py:18 | ControlFlowNode for sys | Module sys | builtin-class module | 17 | +| i_imports.py:23 | ControlFlowNode for ImportExpr | Module code | builtin-class module | 23 | +| i_imports.py:23 | ControlFlowNode for code | Module code | builtin-class module | 23 | +| i_imports.py:24 | ControlFlowNode for Attribute | Module code.package.x | builtin-class module | 0 | +| i_imports.py:24 | ControlFlowNode for code | Module code | builtin-class module | 23 | +| i_imports.py:27 | ControlFlowNode for ImportExpr | Module code.test_package | builtin-class module | 27 | +| i_imports.py:29 | ControlFlowNode for ImportExpr | Module _io | builtin-class module | 29 | +| i_imports.py:29 | ControlFlowNode for _io | Module _io | builtin-class module | 29 | +| i_imports.py:30 | ControlFlowNode for Attribute | builtin-class _io.StringIO | builtin-class type | 30 | +| i_imports.py:30 | ControlFlowNode for StringIO | builtin-class _io.StringIO | builtin-class type | 30 | +| i_imports.py:30 | ControlFlowNode for _io | Module _io | builtin-class module | 29 | +| i_imports.py:31 | ControlFlowNode for Attribute | builtin-class _io.BytesIO | builtin-class type | 31 | +| i_imports.py:31 | ControlFlowNode for BytesIO | builtin-class _io.BytesIO | builtin-class type | 31 | +| i_imports.py:31 | ControlFlowNode for _io | Module _io | builtin-class module | 29 | +| i_imports.py:33 | ControlFlowNode for ImportExpr | Module io | builtin-class module | 33 | +| i_imports.py:33 | ControlFlowNode for io | Module io | builtin-class module | 33 | +| i_imports.py:34 | ControlFlowNode for Attribute | builtin-class _io.StringIO | builtin-class type | 55 | +| i_imports.py:34 | ControlFlowNode for StringIO | builtin-class _io.StringIO | builtin-class type | 55 | +| i_imports.py:34 | ControlFlowNode for io | Module io | builtin-class module | 33 | +| i_imports.py:35 | ControlFlowNode for Attribute | builtin-class _io.BytesIO | builtin-class type | 55 | +| i_imports.py:35 | ControlFlowNode for BytesIO | builtin-class _io.BytesIO | builtin-class type | 55 | +| i_imports.py:35 | ControlFlowNode for io | Module io | builtin-class module | 33 | +| i_imports.py:37 | ControlFlowNode for ImportExpr | Module code | builtin-class module | 37 | +| i_imports.py:37 | ControlFlowNode for code | Module code | builtin-class module | 37 | +| i_imports.py:38 | ControlFlowNode for Attribute | Function f2 | builtin-class function | 24 | +| i_imports.py:38 | ControlFlowNode for Attribute | Module code.n_nesting | builtin-class module | 0 | +| i_imports.py:38 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 24 | +| i_imports.py:38 | ControlFlowNode for code | Module code | builtin-class module | 37 | +| j_convoluted_imports.py:2 | ControlFlowNode for ImportExpr | Module code.package | builtin-class module | 2 | +| j_convoluted_imports.py:3 | ControlFlowNode for ImportMember | Function module | builtin-class function | 2 | +| j_convoluted_imports.py:3 | ControlFlowNode for module | Function module | builtin-class function | 2 | +| j_convoluted_imports.py:5 | ControlFlowNode for ImportExpr | Module code.package | builtin-class module | 5 | +| j_convoluted_imports.py:6 | ControlFlowNode for ImportMember | Module code.package.x | builtin-class module | 0 | +| j_convoluted_imports.py:6 | ControlFlowNode for x | Module code.package.x | builtin-class module | 0 | +| j_convoluted_imports.py:9 | ControlFlowNode for C | class C | builtin-class type | 9 | +| j_convoluted_imports.py:9 | ControlFlowNode for ClassExpr | class C | builtin-class type | 9 | +| j_convoluted_imports.py:9 | ControlFlowNode for object | builtin-class object | builtin-class type | 9 | +| j_convoluted_imports.py:11 | ControlFlowNode for ImportExpr | Module code.package | builtin-class module | 11 | +| j_convoluted_imports.py:11 | ControlFlowNode for ImportMember | int 7 | builtin-class int | 5 | +| j_convoluted_imports.py:11 | ControlFlowNode for module2 | int 7 | builtin-class int | 5 | +| j_convoluted_imports.py:13 | ControlFlowNode for FunctionExpr | Function f | builtin-class function | 13 | +| j_convoluted_imports.py:13 | ControlFlowNode for f | Function f | builtin-class function | 13 | +| j_convoluted_imports.py:14 | ControlFlowNode for ImportExpr | Module code.package | builtin-class module | 14 | +| j_convoluted_imports.py:14 | ControlFlowNode for ImportMember | Module code.package.x | builtin-class module | 0 | +| j_convoluted_imports.py:14 | ControlFlowNode for x | Module code.package.x | builtin-class module | 0 | +| j_convoluted_imports.py:16 | ControlFlowNode for ImportExpr | Module code.package | builtin-class module | 16 | +| j_convoluted_imports.py:16 | ControlFlowNode for ImportMember | Module code.package.moduleX | builtin-class module | 0 | +| j_convoluted_imports.py:16 | ControlFlowNode for moduleX | Module code.package.moduleX | builtin-class module | 0 | +| j_convoluted_imports.py:17 | ControlFlowNode for Attribute | class Y | builtin-class type | 1 | +| j_convoluted_imports.py:17 | ControlFlowNode for moduleX | Module code.package.moduleX | builtin-class module | 0 | +| k_getsetattr.py:4 | ControlFlowNode for C | class C | builtin-class type | 4 | +| k_getsetattr.py:4 | ControlFlowNode for ClassExpr | class C | builtin-class type | 4 | +| k_getsetattr.py:4 | ControlFlowNode for object | builtin-class object | builtin-class type | 4 | +| k_getsetattr.py:6 | ControlFlowNode for FunctionExpr | Function meth1 | builtin-class function | 6 | +| k_getsetattr.py:6 | ControlFlowNode for meth1 | Function meth1 | builtin-class function | 6 | +| k_getsetattr.py:7 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 7 | +| k_getsetattr.py:7 | ControlFlowNode for Str | 'a' | builtin-class str | 7 | +| k_getsetattr.py:7 | ControlFlowNode for self | self | class C | 6 | +| k_getsetattr.py:7 | ControlFlowNode for self | self | class C | 12 | +| k_getsetattr.py:7 | ControlFlowNode for setattr | Builtin-function setattr | builtin-class builtin_function_or_method | 7 | +| k_getsetattr.py:7 | ControlFlowNode for setattr() | NoneType None | builtin-class NoneType | 7 | +| k_getsetattr.py:8 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 8 | +| k_getsetattr.py:8 | ControlFlowNode for Str | 'b' | builtin-class str | 8 | +| k_getsetattr.py:8 | ControlFlowNode for self | self | class C | 6 | +| k_getsetattr.py:8 | ControlFlowNode for self | self | class C | 12 | +| k_getsetattr.py:8 | ControlFlowNode for setattr | Builtin-function setattr | builtin-class builtin_function_or_method | 8 | +| k_getsetattr.py:8 | ControlFlowNode for setattr() | NoneType None | builtin-class NoneType | 8 | +| k_getsetattr.py:9 | ControlFlowNode for Str | 'a' | builtin-class str | 9 | +| k_getsetattr.py:9 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 9 | +| k_getsetattr.py:9 | ControlFlowNode for getattr() | int 0 | builtin-class int | 7 | +| k_getsetattr.py:9 | ControlFlowNode for self | self | class C | 6 | +| k_getsetattr.py:9 | ControlFlowNode for self | self | class C | 12 | +| k_getsetattr.py:10 | ControlFlowNode for Str | 'c' | builtin-class str | 10 | +| k_getsetattr.py:10 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 10 | +| k_getsetattr.py:10 | ControlFlowNode for getattr() | int 2 | builtin-class int | 14 | +| k_getsetattr.py:10 | ControlFlowNode for self | self | class C | 6 | +| k_getsetattr.py:10 | ControlFlowNode for self | self | class C | 12 | +| k_getsetattr.py:12 | ControlFlowNode for FunctionExpr | Function meth2 | builtin-class function | 12 | +| k_getsetattr.py:12 | ControlFlowNode for meth2 | Function meth2 | builtin-class function | 12 | +| k_getsetattr.py:13 | ControlFlowNode for FloatLiteral | float 7.0 | builtin-class float | 13 | +| k_getsetattr.py:13 | ControlFlowNode for Str | 'a' | builtin-class str | 13 | +| k_getsetattr.py:13 | ControlFlowNode for self | self | class C | 12 | +| k_getsetattr.py:13 | ControlFlowNode for setattr | Builtin-function setattr | builtin-class builtin_function_or_method | 13 | +| k_getsetattr.py:13 | ControlFlowNode for setattr() | NoneType None | builtin-class NoneType | 13 | +| k_getsetattr.py:14 | ControlFlowNode for IntegerLiteral | int 2 | builtin-class int | 14 | +| k_getsetattr.py:14 | ControlFlowNode for Str | 'c' | builtin-class str | 14 | +| k_getsetattr.py:14 | ControlFlowNode for self | self | class C | 12 | +| k_getsetattr.py:14 | ControlFlowNode for setattr | Builtin-function setattr | builtin-class builtin_function_or_method | 14 | +| k_getsetattr.py:14 | ControlFlowNode for setattr() | NoneType None | builtin-class NoneType | 14 | +| k_getsetattr.py:15 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 6 | +| k_getsetattr.py:15 | ControlFlowNode for self | self | class C | 12 | +| k_getsetattr.py:16 | ControlFlowNode for Str | 'a' | builtin-class str | 16 | +| k_getsetattr.py:16 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 16 | +| k_getsetattr.py:16 | ControlFlowNode for getattr() | int 0 | builtin-class int | 7 | +| k_getsetattr.py:16 | ControlFlowNode for self | self | class C | 12 | +| k_getsetattr.py:17 | ControlFlowNode for Str | 'b' | builtin-class str | 17 | +| k_getsetattr.py:17 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 17 | +| k_getsetattr.py:17 | ControlFlowNode for getattr() | int 1 | builtin-class int | 8 | +| k_getsetattr.py:17 | ControlFlowNode for self | self | class C | 12 | +| k_getsetattr.py:18 | ControlFlowNode for Str | 'c' | builtin-class str | 18 | +| k_getsetattr.py:18 | ControlFlowNode for getattr | Builtin-function getattr | builtin-class builtin_function_or_method | 18 | +| k_getsetattr.py:18 | ControlFlowNode for getattr() | int 2 | builtin-class int | 14 | +| k_getsetattr.py:18 | ControlFlowNode for self | self | class C | 12 | +| k_getsetattr.py:21 | ControlFlowNode for FunctionExpr | Function k | builtin-class function | 21 | +| k_getsetattr.py:21 | ControlFlowNode for k | Function k | builtin-class function | 21 | +| k_getsetattr.py:22 | ControlFlowNode for C | class C | builtin-class type | 4 | +| k_getsetattr.py:22 | ControlFlowNode for C() | C() | class C | 22 | +| k_getsetattr.py:22 | ControlFlowNode for c1 | C() | class C | 22 | +| k_getsetattr.py:23 | ControlFlowNode for C | class C | builtin-class type | 4 | +| k_getsetattr.py:23 | ControlFlowNode for C() | C() | class C | 23 | +| k_getsetattr.py:23 | ControlFlowNode for c2 | C() | class C | 23 | +| k_getsetattr.py:24 | ControlFlowNode for C | class C | builtin-class type | 4 | +| k_getsetattr.py:24 | ControlFlowNode for C() | C() | class C | 24 | +| k_getsetattr.py:24 | ControlFlowNode for c3 | C() | class C | 24 | +| k_getsetattr.py:25 | ControlFlowNode for Attribute | int 10 | builtin-class int | 25 | +| k_getsetattr.py:25 | ControlFlowNode for IntegerLiteral | int 10 | builtin-class int | 25 | +| k_getsetattr.py:25 | ControlFlowNode for c1 | C() | class C | 22 | +| k_getsetattr.py:27 | ControlFlowNode for Attribute | int 20 | builtin-class int | 27 | +| k_getsetattr.py:27 | ControlFlowNode for IntegerLiteral | int 20 | builtin-class int | 27 | +| k_getsetattr.py:27 | ControlFlowNode for c2 | C() | class C | 23 | +| k_getsetattr.py:28 | ControlFlowNode for Attribute | int 10 | builtin-class int | 25 | +| k_getsetattr.py:28 | ControlFlowNode for c1 | C() | class C | 22 | +| k_getsetattr.py:29 | ControlFlowNode for Attribute | int 20 | builtin-class int | 27 | +| k_getsetattr.py:29 | ControlFlowNode for c2 | C() | class C | 23 | +| k_getsetattr.py:30 | ControlFlowNode for c3 | C() | class C | 24 | +| k_getsetattr.py:31 | ControlFlowNode for Attribute | int 30 | builtin-class int | 31 | +| k_getsetattr.py:31 | ControlFlowNode for IntegerLiteral | int 30 | builtin-class int | 31 | +| k_getsetattr.py:31 | ControlFlowNode for c3 | C() | class C | 24 | +| l_calls.py:3 | ControlFlowNode for FunctionExpr | Function foo | builtin-class function | 3 | +| l_calls.py:3 | ControlFlowNode for List | List | builtin-class list | 3 | +| l_calls.py:3 | ControlFlowNode for foo | Function foo | builtin-class function | 3 | +| l_calls.py:4 | ControlFlowNode for Attribute() | NoneType None | builtin-class NoneType | 4 | +| l_calls.py:4 | ControlFlowNode for Str | 'x' | builtin-class str | 4 | +| l_calls.py:4 | ControlFlowNode for x | List | builtin-class list | 3 | +| l_calls.py:6 | ControlFlowNode for FunctionExpr | Function bar | builtin-class function | 6 | +| l_calls.py:6 | ControlFlowNode for List | List | builtin-class list | 6 | +| l_calls.py:6 | ControlFlowNode for bar | Function bar | builtin-class function | 6 | +| l_calls.py:7 | ControlFlowNode for len | Builtin-function len | builtin-class builtin_function_or_method | 7 | +| l_calls.py:7 | ControlFlowNode for len() | len() | builtin-class int | 7 | +| l_calls.py:7 | ControlFlowNode for x | List | builtin-class list | 6 | +| l_calls.py:9 | ControlFlowNode for foo | Function foo | builtin-class function | 3 | +| l_calls.py:9 | ControlFlowNode for foo() | NoneType None | builtin-class NoneType | 4 | +| l_calls.py:10 | ControlFlowNode for bar | Function bar | builtin-class function | 6 | +| l_calls.py:10 | ControlFlowNode for bar() | len() | builtin-class int | 7 | +| l_calls.py:12 | ControlFlowNode for ClassExpr | class Owner | builtin-class type | 12 | +| l_calls.py:12 | ControlFlowNode for Owner | class Owner | builtin-class type | 12 | +| l_calls.py:12 | ControlFlowNode for object | builtin-class object | builtin-class type | 12 | +| l_calls.py:14 | ControlFlowNode for classmethod | builtin-class classmethod | builtin-class type | 14 | +| l_calls.py:14 | ControlFlowNode for classmethod() | classmethod() | builtin-class classmethod | 14 | +| l_calls.py:15 | ControlFlowNode for FunctionExpr | Function cm | builtin-class function | 15 | +| l_calls.py:15 | ControlFlowNode for cm | classmethod() | builtin-class classmethod | 14 | +| l_calls.py:16 | ControlFlowNode for cls | class Owner | builtin-class type | 23 | +| l_calls.py:18 | ControlFlowNode for classmethod | builtin-class classmethod | builtin-class type | 18 | +| l_calls.py:18 | ControlFlowNode for classmethod() | classmethod() | builtin-class classmethod | 18 | +| l_calls.py:19 | ControlFlowNode for FunctionExpr | Function cm2 | builtin-class function | 19 | +| l_calls.py:19 | ControlFlowNode for cm2 | classmethod() | builtin-class classmethod | 18 | +| l_calls.py:20 | ControlFlowNode for arg | int 1 | builtin-class int | 25 | +| l_calls.py:23 | ControlFlowNode for FunctionExpr | Function m | builtin-class function | 23 | +| l_calls.py:23 | ControlFlowNode for m | Function m | builtin-class function | 23 | +| l_calls.py:24 | ControlFlowNode for Attribute() | class Owner | builtin-class type | 23 | +| l_calls.py:24 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 24 | +| l_calls.py:24 | ControlFlowNode for a | class Owner | builtin-class type | 23 | +| l_calls.py:24 | ControlFlowNode for self | self | class Owner | 23 | +| l_calls.py:25 | ControlFlowNode for Attribute() | int 1 | builtin-class int | 25 | +| l_calls.py:25 | ControlFlowNode for IntegerLiteral | int 1 | builtin-class int | 25 | +| l_calls.py:25 | ControlFlowNode for a | class Owner | builtin-class type | 23 | +| s_scopes.py:4 | ControlFlowNode for True | bool True | builtin-class bool | 4 | +| s_scopes.py:4 | ControlFlowNode for float | bool True | builtin-class bool | 4 | +| s_scopes.py:7 | ControlFlowNode for C2 | class C2 | builtin-class type | 7 | +| s_scopes.py:7 | ControlFlowNode for ClassExpr | class C2 | builtin-class type | 7 | +| s_scopes.py:7 | ControlFlowNode for object | builtin-class object | builtin-class type | 7 | +| s_scopes.py:9 | ControlFlowNode for i1 | builtin-class int | builtin-class type | 9 | +| s_scopes.py:9 | ControlFlowNode for int | builtin-class int | builtin-class type | 9 | +| s_scopes.py:10 | ControlFlowNode for f1 | bool True | builtin-class bool | 4 | +| s_scopes.py:10 | ControlFlowNode for f1 | builtin-class float | builtin-class type | 10 | +| s_scopes.py:10 | ControlFlowNode for float | bool True | builtin-class bool | 4 | +| s_scopes.py:10 | ControlFlowNode for float | builtin-class float | builtin-class type | 10 | +| s_scopes.py:12 | ControlFlowNode for IntegerLiteral | int 0 | builtin-class int | 12 | +| s_scopes.py:12 | ControlFlowNode for int | int 0 | builtin-class int | 12 | +| s_scopes.py:15 | ControlFlowNode for FloatLiteral | float 1.0 | builtin-class float | 15 | +| s_scopes.py:15 | ControlFlowNode for str | float 1.0 | builtin-class float | 15 | +| s_scopes.py:17 | ControlFlowNode for None | NoneType None | builtin-class NoneType | 17 | +| s_scopes.py:17 | ControlFlowNode for float | NoneType None | builtin-class NoneType | 17 | +| s_scopes.py:18 | ControlFlowNode for i2 | int 0 | builtin-class int | 12 | +| s_scopes.py:18 | ControlFlowNode for int | int 0 | builtin-class int | 12 | +| s_scopes.py:19 | ControlFlowNode for s | builtin-class str | builtin-class type | 19 | +| s_scopes.py:19 | ControlFlowNode for s | float 1.0 | builtin-class float | 15 | +| s_scopes.py:19 | ControlFlowNode for str | builtin-class str | builtin-class type | 19 | +| s_scopes.py:19 | ControlFlowNode for str | float 1.0 | builtin-class float | 15 | +| s_scopes.py:20 | ControlFlowNode for f2 | NoneType None | builtin-class NoneType | 17 | +| s_scopes.py:20 | ControlFlowNode for f2 | bool True | builtin-class bool | 4 | +| s_scopes.py:20 | ControlFlowNode for f2 | builtin-class float | builtin-class type | 20 | +| s_scopes.py:20 | ControlFlowNode for float | NoneType None | builtin-class NoneType | 17 | +| s_scopes.py:20 | ControlFlowNode for float | bool True | builtin-class bool | 4 | +| s_scopes.py:20 | ControlFlowNode for float | builtin-class float | builtin-class type | 20 | +| s_scopes.py:23 | ControlFlowNode for i | builtin-class int | builtin-class type | 23 | +| s_scopes.py:23 | ControlFlowNode for int | builtin-class int | builtin-class type | 23 | +| s_scopes.py:24 | ControlFlowNode for f | bool True | builtin-class bool | 4 | +| s_scopes.py:24 | ControlFlowNode for f | builtin-class float | builtin-class type | 24 | +| s_scopes.py:24 | ControlFlowNode for float | bool True | builtin-class bool | 4 | +| s_scopes.py:24 | ControlFlowNode for float | builtin-class float | builtin-class type | 24 | diff --git a/python/ql/test/library-tests/PointsTo/new/PointsToWithType.ql b/python/ql/test/library-tests/PointsTo/new/PointsToWithType.ql new file mode 100644 index 000000000000..0c845f6bc3c5 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/PointsToWithType.ql @@ -0,0 +1,9 @@ +import python +import Util +import semmle.python.pointsto.PointsTo + +from ControlFlowNode f, Object o, ClassObject c, ControlFlowNode x + +where PointsTo::points_to(f, _, o, c, x) + +select locate(f.getLocation(), "abdeghijkls"), f.toString(), repr(o), repr(c), x.getLocation().getStartLine() diff --git a/python/ql/test/library-tests/PointsTo/new/Precedes.expected b/python/ql/test/library-tests/PointsTo/new/Precedes.expected new file mode 100644 index 000000000000..fc1a262139ee --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Precedes.expected @@ -0,0 +1,12 @@ +| q_super.py:0 | Module code.q_super | q_super.py:3 | Function __init__ | +| q_super.py:0 | Module code.q_super | q_super.py:10 | Function __init__ | +| q_super.py:0 | Module code.q_super | q_super.py:16 | Function meth | +| q_super.py:0 | Module code.q_super | q_super.py:21 | Function meth | +| q_super.py:0 | Module code.q_super | q_super.py:26 | Function meth | +| q_super.py:0 | Module code.q_super | q_super.py:31 | Function meth | +| q_super.py:0 | Module code.q_super | q_super.py:37 | Function meth | +| q_super.py:0 | Module code.q_super | q_super.py:43 | Function __init__ | +| q_super.py:0 | Module code.q_super | q_super.py:50 | Function __init__ | +| q_super.py:0 | Module code.q_super | q_super.py:57 | Function __init__ | +| q_super.py:0 | Module code.q_super | q_super.py:65 | Function __init__ | +| q_super.py:0 | Module code.q_super | q_super.py:73 | Function __init__ | diff --git a/python/ql/test/library-tests/PointsTo/new/Precedes.ql b/python/ql/test/library-tests/PointsTo/new/Precedes.ql new file mode 100644 index 000000000000..959ec181f5f7 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Precedes.ql @@ -0,0 +1,8 @@ + +import python +import Util + +from Scope pre, Scope post +where pre.precedes(post) + +select locate(pre.getLocation(), "q"), pre.toString(), locate(post.getLocation(), "q"), post.toString() diff --git a/python/ql/test/library-tests/PointsTo/new/README.md b/python/ql/test/library-tests/PointsTo/new/README.md new file mode 100644 index 000000000000..0934a6cf72c4 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/README.md @@ -0,0 +1,14 @@ +## Dataflow, points-to, call-graph and type-inference tests. + +Since dataflow, points-to, call-graph and type-inference are all interlinked it makes sense to test them together. + +### The test code. +The test code is all under the `code/` subdirectory and all test files are named \w_name, supporting +files do have an underscore as their second character. +This allows tests to be applied to a subset of the test data and test/data combinations to be turned on/off easily for debugging. + +Be aware that here are two `__init__.py`, so the results are interleaved. + + + + diff --git a/python/ql/test/library-tests/PointsTo/new/Reachable.expected b/python/ql/test/library-tests/PointsTo/new/Reachable.expected new file mode 100644 index 000000000000..c7dbbb816a4d --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Reachable.expected @@ -0,0 +1,80 @@ +| m_attributes.py:0 | Entry node for Module code.m_attributes | import | +| m_attributes.py:0 | Exit node for Module code.m_attributes | import | +| m_attributes.py:3 | ControlFlowNode for C | import | +| m_attributes.py:3 | ControlFlowNode for ClassExpr | import | +| m_attributes.py:3 | ControlFlowNode for object | import | +| m_attributes.py:3 | Entry node for Class C | import | +| m_attributes.py:3 | Exit node for Class C | import | +| m_attributes.py:5 | ControlFlowNode for FunctionExpr | import | +| m_attributes.py:5 | ControlFlowNode for IntegerLiteral | import | +| m_attributes.py:5 | ControlFlowNode for __init__ | import | +| m_attributes.py:5 | ControlFlowNode for a | code/m_attributes.py:12 from import | +| m_attributes.py:5 | ControlFlowNode for a | code/m_attributes.py:12 from import | +| m_attributes.py:5 | ControlFlowNode for a | code/m_attributes.py:13 from import | +| m_attributes.py:5 | ControlFlowNode for a | code/m_attributes.py:13 from import | +| m_attributes.py:5 | ControlFlowNode for a | runtime | +| m_attributes.py:5 | ControlFlowNode for self | code/m_attributes.py:12 from import | +| m_attributes.py:5 | ControlFlowNode for self | code/m_attributes.py:12 from import | +| m_attributes.py:5 | ControlFlowNode for self | code/m_attributes.py:13 from import | +| m_attributes.py:5 | ControlFlowNode for self | code/m_attributes.py:13 from import | +| m_attributes.py:5 | ControlFlowNode for self | runtime | +| m_attributes.py:5 | Entry node for Function __init__ | code/m_attributes.py:12 from import | +| m_attributes.py:5 | Entry node for Function __init__ | code/m_attributes.py:12 from import | +| m_attributes.py:5 | Entry node for Function __init__ | code/m_attributes.py:13 from import | +| m_attributes.py:5 | Entry node for Function __init__ | code/m_attributes.py:13 from import | +| m_attributes.py:5 | Entry node for Function __init__ | runtime | +| m_attributes.py:5 | Exit node for Function __init__ | code/m_attributes.py:12 from import | +| m_attributes.py:5 | Exit node for Function __init__ | code/m_attributes.py:12 from import | +| m_attributes.py:5 | Exit node for Function __init__ | code/m_attributes.py:13 from import | +| m_attributes.py:5 | Exit node for Function __init__ | code/m_attributes.py:13 from import | +| m_attributes.py:5 | Exit node for Function __init__ | runtime | +| m_attributes.py:6 | ControlFlowNode for Attribute | code/m_attributes.py:12 from import | +| m_attributes.py:6 | ControlFlowNode for Attribute | code/m_attributes.py:12 from import | +| m_attributes.py:6 | ControlFlowNode for Attribute | code/m_attributes.py:13 from import | +| m_attributes.py:6 | ControlFlowNode for Attribute | code/m_attributes.py:13 from import | +| m_attributes.py:6 | ControlFlowNode for Attribute | runtime | +| m_attributes.py:6 | ControlFlowNode for a | code/m_attributes.py:12 from import | +| m_attributes.py:6 | ControlFlowNode for a | code/m_attributes.py:12 from import | +| m_attributes.py:6 | ControlFlowNode for a | code/m_attributes.py:13 from import | +| m_attributes.py:6 | ControlFlowNode for a | code/m_attributes.py:13 from import | +| m_attributes.py:6 | ControlFlowNode for a | runtime | +| m_attributes.py:6 | ControlFlowNode for self | code/m_attributes.py:12 from import | +| m_attributes.py:6 | ControlFlowNode for self | code/m_attributes.py:12 from import | +| m_attributes.py:6 | ControlFlowNode for self | code/m_attributes.py:13 from import | +| m_attributes.py:6 | ControlFlowNode for self | code/m_attributes.py:13 from import | +| m_attributes.py:6 | ControlFlowNode for self | runtime | +| m_attributes.py:8 | ControlFlowNode for FunctionExpr | import | +| m_attributes.py:8 | ControlFlowNode for foo | import | +| m_attributes.py:8 | ControlFlowNode for other | code/m_attributes.py:12 from import | +| m_attributes.py:8 | ControlFlowNode for other | code/m_attributes.py:13 from import | +| m_attributes.py:8 | ControlFlowNode for other | runtime | +| m_attributes.py:8 | ControlFlowNode for self | code/m_attributes.py:12 from import | +| m_attributes.py:8 | ControlFlowNode for self | code/m_attributes.py:13 from import | +| m_attributes.py:8 | ControlFlowNode for self | runtime | +| m_attributes.py:8 | Entry node for Function foo | code/m_attributes.py:12 from import | +| m_attributes.py:8 | Entry node for Function foo | code/m_attributes.py:13 from import | +| m_attributes.py:8 | Entry node for Function foo | runtime | +| m_attributes.py:8 | Exit node for Function foo | code/m_attributes.py:12 from import | +| m_attributes.py:8 | Exit node for Function foo | code/m_attributes.py:13 from import | +| m_attributes.py:8 | Exit node for Function foo | runtime | +| m_attributes.py:9 | ControlFlowNode for Attribute | code/m_attributes.py:12 from import | +| m_attributes.py:9 | ControlFlowNode for Attribute | code/m_attributes.py:13 from import | +| m_attributes.py:9 | ControlFlowNode for Attribute | runtime | +| m_attributes.py:9 | ControlFlowNode for self | code/m_attributes.py:12 from import | +| m_attributes.py:9 | ControlFlowNode for self | code/m_attributes.py:13 from import | +| m_attributes.py:9 | ControlFlowNode for self | runtime | +| m_attributes.py:10 | ControlFlowNode for Attribute | code/m_attributes.py:12 from import | +| m_attributes.py:10 | ControlFlowNode for Attribute | code/m_attributes.py:13 from import | +| m_attributes.py:10 | ControlFlowNode for Attribute | runtime | +| m_attributes.py:10 | ControlFlowNode for other | code/m_attributes.py:12 from import | +| m_attributes.py:10 | ControlFlowNode for other | code/m_attributes.py:13 from import | +| m_attributes.py:10 | ControlFlowNode for other | runtime | +| m_attributes.py:12 | ControlFlowNode for Attribute | import | +| m_attributes.py:12 | ControlFlowNode for Attribute() | import | +| m_attributes.py:12 | ControlFlowNode for C | import | +| m_attributes.py:12 | ControlFlowNode for C() | import | +| m_attributes.py:13 | ControlFlowNode for Attribute | import | +| m_attributes.py:13 | ControlFlowNode for Attribute() | import | +| m_attributes.py:13 | ControlFlowNode for C | import | +| m_attributes.py:13 | ControlFlowNode for C() | import | +| m_attributes.py:13 | ControlFlowNode for IntegerLiteral | import | diff --git a/python/ql/test/library-tests/PointsTo/new/Reachable.ql b/python/ql/test/library-tests/PointsTo/new/Reachable.ql new file mode 100644 index 000000000000..60fccc308ee8 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Reachable.ql @@ -0,0 +1,8 @@ + +import python +private import semmle.python.pointsto.PointsTo +import Util + +from ControlFlowNode f, Context ctx +where PointsTo::Test::reachableBlock(f.getBasicBlock(), ctx) +select locate(f.getLocation(), "m"), f.toString(), ctx diff --git a/python/ql/test/library-tests/PointsTo/new/SSA.expected b/python/ql/test/library-tests/PointsTo/new/SSA.expected new file mode 100644 index 000000000000..18df1e455dc2 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/SSA.expected @@ -0,0 +1,624 @@ +| __init__.py:0 | __name___0 = ScopeEntryDefinition | 'code' | builtin-class str | +| __init__.py:0 | __name___0 = ScopeEntryDefinition | 'code.package' | builtin-class str | +| __init__.py:0 | __name___0 = ScopeEntryDefinition | 'code.test_package' | builtin-class str | +| __init__.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| __init__.py:0 | module2_0 = ImplicitSubModuleDefinition | Module code.package.module2 | builtin-class module | +| __init__.py:0 | moduleX_0 = ImplicitSubModuleDefinition | Module code.package.moduleX | builtin-class module | +| __init__.py:0 | sys_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| __init__.py:1 | __name___1 = ImportStarRefinement(__name___0) | 'code.test_package' | builtin-class str | +| __init__.py:1 | __package___1 = ImportStarRefinement(__package___0) | *UNDEFINED* | *UNKNOWN TYPE* | +| __init__.py:1 | sys_1 = ImportStarRefinement(sys_0) | *UNDEFINED* | *UNKNOWN TYPE* | +| __init__.py:2 | __name___2 = ImportStarRefinement(__name___1) | 'code.test_package' | builtin-class str | +| __init__.py:2 | __package___2 = ImportStarRefinement(__package___1) | *UNDEFINED* | *UNKNOWN TYPE* | +| __init__.py:2 | module_0 = ImportMember | Function module | builtin-class function | +| __init__.py:3 | sys_2 = ImportExpr | Module sys | builtin-class module | +| __init__.py:4 | module3_0 = ImportMember | Module code.package.module2 | builtin-class module | +| __init__.py:5 | module2_1 = IntegerLiteral | int 7 | builtin-class int | +| __init__.py:6 | module4_0 = ImportMember | int 7 | builtin-class int | +| __init__.py:7 | module5_0 = ImportMember | Module code.package.module2 | builtin-class module | +| __init__.py:8 | moduleX_1 = ImportMember | Module code.package.moduleX | builtin-class module | +| a_simple.py:0 | __name___0 = ScopeEntryDefinition | 'code.a_simple' | builtin-class str | +| a_simple.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| a_simple.py:2 | f1_0 = FloatLiteral | float 1.0 | builtin-class float | +| a_simple.py:5 | i1_0 = IntegerLiteral | int 0 | builtin-class int | +| a_simple.py:6 | s_0 = Tuple | Tuple | builtin-class tuple | +| a_simple.py:8 | func_0 = FunctionExpr | Function func | builtin-class function | +| a_simple.py:11 | C_0 = ClassExpr | class C | builtin-class type | +| a_simple.py:14 | d_0 = ParameterDefinition | d | builtin-class dict | +| a_simple.py:14 | t_0 = ParameterDefinition | t | builtin-class tuple | +| a_simple.py:14 | vararg_kwarg_0 = FunctionExpr | Function vararg_kwarg | builtin-class function | +| a_simple.py:18 | multi_loop_0 = FunctionExpr | Function multi_loop | builtin-class function | +| a_simple.py:18 | y_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| a_simple.py:19 | x_0 = None | NoneType None | builtin-class NoneType | +| a_simple.py:20 | x_1 = phi(x_0, x_2) | NoneType None | builtin-class NoneType | +| a_simple.py:20 | y_1 = phi(y_0, y_2) | *UNDEFINED* | *UNKNOWN TYPE* | +| a_simple.py:23 | with_definition_0 = FunctionExpr | Function with_definition | builtin-class function | +| a_simple.py:27 | multi_loop_in_try_0 = FunctionExpr | Function multi_loop_in_try | builtin-class function | +| a_simple.py:27 | p_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| a_simple.py:27 | q_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| a_simple.py:29 | p_1 = phi(p_0, p_2) | *UNDEFINED* | *UNKNOWN TYPE* | +| a_simple.py:29 | q_1 = phi(q_0, q_2) | *UNDEFINED* | *UNKNOWN TYPE* | +| a_simple.py:34 | args_0 = ParameterDefinition | args | builtin-class tuple | +| a_simple.py:34 | f_0 = FunctionExpr | Function f | builtin-class function | +| a_simple.py:34 | kwargs_0 = ParameterDefinition | kwargs | builtin-class dict | +| b_condition.py:0 | __name___0 = ScopeEntryDefinition | 'code.b_condition' | builtin-class str | +| b_condition.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:0 | double_attr_check_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:0 | g_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:0 | h_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:0 | k_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:0 | loop_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:0 | not_or_not_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:0 | odasa6261_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:0 | split_bool1_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:0 | v2_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:4 | f_0 = FunctionExpr | Function f | builtin-class function | +| b_condition.py:5 | x_0 = IfExp | NoneType None | builtin-class NoneType | +| b_condition.py:8 | x_1 = IntegerLiteral | int 7 | builtin-class int | +| b_condition.py:9 | x_3 = phi(x_1, x_2) | int 7 | builtin-class int | +| b_condition.py:11 | x_4 = IfExp | NoneType None | builtin-class NoneType | +| b_condition.py:14 | x_5 = IntegerLiteral | int 7 | builtin-class int | +| b_condition.py:15 | x_6 = Pi(x_4) [false] | NoneType None | builtin-class NoneType | +| b_condition.py:15 | x_7 = phi(x_5, x_6) | NoneType None | builtin-class NoneType | +| b_condition.py:15 | x_7 = phi(x_5, x_6) | int 7 | builtin-class int | +| b_condition.py:17 | x_8 = IfExp | NoneType None | builtin-class NoneType | +| b_condition.py:20 | x_9 = None | NoneType None | builtin-class NoneType | +| b_condition.py:21 | x_11 = phi(x_9, x_10) | NoneType None | builtin-class NoneType | +| b_condition.py:23 | x_12 = IfExp | NoneType None | builtin-class NoneType | +| b_condition.py:25 | x_14 = IfExp | int 1 | builtin-class int | +| b_condition.py:26 | x_15 = ArgumentRefinement(x_14) | int 1 | builtin-class int | +| b_condition.py:28 | x_16 = IntegerLiteral | int 1 | builtin-class int | +| b_condition.py:29 | x_17 = phi(x_15, x_16) | int 1 | builtin-class int | +| b_condition.py:31 | x_18 = IfExp | int 1 | builtin-class int | +| b_condition.py:33 | x_19 = IntegerLiteral | int 7 | builtin-class int | +| b_condition.py:34 | x_20 = Pi(x_18) [false] | int 1 | builtin-class int | +| b_condition.py:34 | x_21 = phi(x_19, x_20) | int 1 | builtin-class int | +| b_condition.py:34 | x_21 = phi(x_19, x_20) | int 7 | builtin-class int | +| b_condition.py:34 | x_22 = ArgumentRefinement(x_21) | int 1 | builtin-class int | +| b_condition.py:34 | x_22 = ArgumentRefinement(x_21) | int 7 | builtin-class int | +| b_condition.py:36 | x_23 = ArgumentRefinement(x_22) | int 1 | builtin-class int | +| b_condition.py:36 | x_23 = ArgumentRefinement(x_22) | int 7 | builtin-class int | +| b_condition.py:36 | x_24 = Pi(x_23) [true] | int 1 | builtin-class int | +| b_condition.py:36 | x_24 = Pi(x_23) [true] | int 7 | builtin-class int | +| b_condition.py:37 | x_25 = ArgumentRefinement(x_24) | int 1 | builtin-class int | +| b_condition.py:37 | x_25 = ArgumentRefinement(x_24) | int 7 | builtin-class int | +| b_condition.py:50 | g_1 = FunctionExpr | Function g | builtin-class function | +| b_condition.py:55 | loop_1 = FunctionExpr | Function loop | builtin-class function | +| b_condition.py:55 | v_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:56 | v_2 = phi(v_0, v_1, v_5) | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:61 | double_attr_check_1 = FunctionExpr | Function double_attr_check | builtin-class function | +| b_condition.py:69 | h_1 = FunctionExpr | Function h | builtin-class function | +| b_condition.py:70 | b_0 = IfExp | bool True | builtin-class bool | +| b_condition.py:72 | b_1 = IntegerLiteral | int 7 | builtin-class int | +| b_condition.py:73 | b_2 = Pi(b_0) [false] | bool True | builtin-class bool | +| b_condition.py:73 | b_3 = phi(b_1, b_2) | bool True | builtin-class bool | +| b_condition.py:73 | b_3 = phi(b_1, b_2) | int 7 | builtin-class int | +| b_condition.py:75 | k_1 = FunctionExpr | Function k | builtin-class function | +| b_condition.py:76 | t_0 = type | builtin-class type | builtin-class type | +| b_condition.py:78 | t_1 = object | builtin-class object | builtin-class type | +| b_condition.py:79 | t_3 = phi(t_1, t_2) | builtin-class object | builtin-class type | +| b_condition.py:79 | t_4 = ArgumentRefinement(t_3) | builtin-class object | builtin-class type | +| b_condition.py:81 | bar_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:81 | bar_2 = phi(bar_0, bar_1) | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:81 | bar_2 = phi(bar_0, bar_1) | Function bar | builtin-class function | +| b_condition.py:81 | foo_0 = ParameterDefinition | bool True | builtin-class bool | +| b_condition.py:81 | foo_4 = Pi(foo_1) [false] | bool True | builtin-class bool | +| b_condition.py:81 | foo_5 = phi(foo_2, foo_4) | bool True | builtin-class bool | +| b_condition.py:81 | odasa6261_1 = FunctionExpr | Function odasa6261 | builtin-class function | +| b_condition.py:82 | foo_1 = ArgumentRefinement(foo_0) | bool True | builtin-class bool | +| b_condition.py:83 | bar_1 = FunctionExpr | Function bar | builtin-class function | +| b_condition.py:83 | foo_3 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| b_condition.py:87 | split_bool1_1 = FunctionExpr | Function split_bool1 | builtin-class function | +| b_condition.py:87 | x_0 = ParameterDefinition | NoneType None | builtin-class NoneType | +| b_condition.py:87 | y_0 = ParameterDefinition | NoneType None | builtin-class NoneType | +| b_condition.py:90 | x_4 = Pi(x_0) [false] | NoneType None | builtin-class NoneType | +| b_condition.py:90 | x_5 = SingleSuccessorGuard(x_4) [false] | NoneType None | builtin-class NoneType | +| b_condition.py:90 | y_4 = Pi(y_0) [false] | NoneType None | builtin-class NoneType | +| b_condition.py:92 | x_2 = SingleSuccessorGuard(x_5) [false] | NoneType None | builtin-class NoneType | +| b_condition.py:93 | y_5 = ArgumentRefinement(y_4) | NoneType None | builtin-class NoneType | +| b_condition.py:96 | y_6 = SingleSuccessorGuard(y_5) [false] | NoneType None | builtin-class NoneType | +| b_condition.py:97 | x_3 = ArgumentRefinement(x_2) | NoneType None | builtin-class NoneType | +| b_condition.py:101 | a_0 = ParameterDefinition | a | builtin-class tuple | +| b_condition.py:101 | not_or_not_1 = FunctionExpr | Function not_or_not | builtin-class function | +| b_condition.py:102 | a_1 = ArgumentRefinement(a_0) | a | builtin-class tuple | +| b_condition.py:104 | a_2 = Pi(a_1) [false] | a | builtin-class tuple | +| b_condition.py:105 | a_3 = Pi(a_2) [false] | a | builtin-class tuple | +| b_condition.py:107 | a_4 = Pi(a_3) [false] | a | builtin-class tuple | +| c_tests.py:0 | __name___0 = ScopeEntryDefinition | 'code.c_tests' | builtin-class str | +| c_tests.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| c_tests.py:4 | f_0 = FunctionExpr | Function f | builtin-class function | +| c_tests.py:5 | x_0 = IfExp | NoneType None | builtin-class NoneType | +| c_tests.py:10 | x_1 = IfExp | int 0 | builtin-class int | +| c_tests.py:10 | x_1 = IfExp | int 1 | builtin-class int | +| c_tests.py:15 | x_2 = IfExp | int 0 | builtin-class int | +| c_tests.py:15 | x_2 = IfExp | int 1 | builtin-class int | +| c_tests.py:21 | x_3 = IfExp | List | builtin-class list | +| c_tests.py:21 | x_3 = IfExp | Tuple | builtin-class tuple | +| c_tests.py:23 | x_4 = ArgumentRefinement(x_3) | List | builtin-class list | +| c_tests.py:23 | x_4 = ArgumentRefinement(x_3) | Tuple | builtin-class tuple | +| c_tests.py:24 | x_5 = Pi(x_4) [true] | List | builtin-class list | +| c_tests.py:24 | x_5 = Pi(x_4) [true] | Tuple | builtin-class tuple | +| c_tests.py:26 | x_7 = phi(x_5, x_6) | List | builtin-class list | +| c_tests.py:26 | x_7 = phi(x_5, x_6) | Tuple | builtin-class tuple | +| c_tests.py:26 | x_8 = ArgumentRefinement(x_7) | List | builtin-class list | +| c_tests.py:26 | x_8 = ArgumentRefinement(x_7) | Tuple | builtin-class tuple | +| c_tests.py:27 | x_9 = Pi(x_8) [true] | List | builtin-class list | +| c_tests.py:27 | x_9 = Pi(x_8) [true] | Tuple | builtin-class tuple | +| c_tests.py:29 | x_10 = Pi(x_8) [false] | Tuple | builtin-class tuple | +| c_tests.py:29 | x_11 = phi(x_9, x_10) | List | builtin-class list | +| c_tests.py:29 | x_11 = phi(x_9, x_10) | Tuple | builtin-class tuple | +| c_tests.py:29 | x_12 = ArgumentRefinement(x_11) | List | builtin-class list | +| c_tests.py:29 | x_12 = ArgumentRefinement(x_11) | Tuple | builtin-class tuple | +| c_tests.py:30 | x_13 = Pi(x_12) [true] | Tuple | builtin-class tuple | +| c_tests.py:32 | x_14 = Pi(x_12) [false] | List | builtin-class list | +| c_tests.py:32 | x_15 = phi(x_13, x_14) | List | builtin-class list | +| c_tests.py:32 | x_15 = phi(x_13, x_14) | Tuple | builtin-class tuple | +| c_tests.py:56 | others_0 = FunctionExpr | Function others | builtin-class function | +| c_tests.py:56 | x_8 = Pi(x_6) [false] | int 0 | builtin-class int | +| c_tests.py:56 | x_9 = phi(x_7, x_8) | builtin-class float | builtin-class type | +| c_tests.py:56 | x_9 = phi(x_7, x_8) | int 0 | builtin-class int | +| c_tests.py:58 | x_0 = IfExp | builtin-class bool | builtin-class type | +| c_tests.py:58 | x_0 = IfExp | builtin-class type | builtin-class type | +| c_tests.py:63 | x_1 = IfExp | builtin-class float | builtin-class type | +| c_tests.py:63 | x_1 = IfExp | int 0 | builtin-class int | +| c_tests.py:65 | x_2 = ArgumentRefinement(x_1) | builtin-class float | builtin-class type | +| c_tests.py:65 | x_2 = ArgumentRefinement(x_1) | int 0 | builtin-class int | +| c_tests.py:66 | x_3 = Pi(x_2) [true] | int 0 | builtin-class int | +| c_tests.py:68 | x_4 = Pi(x_2) [false] | builtin-class float | builtin-class type | +| c_tests.py:68 | x_5 = phi(x_3, x_4) | builtin-class float | builtin-class type | +| c_tests.py:68 | x_5 = phi(x_3, x_4) | int 0 | builtin-class int | +| c_tests.py:68 | x_6 = ArgumentRefinement(x_5) | builtin-class float | builtin-class type | +| c_tests.py:68 | x_6 = ArgumentRefinement(x_5) | int 0 | builtin-class int | +| c_tests.py:69 | x_7 = Pi(x_6) [true] | builtin-class float | builtin-class type | +| c_tests.py:71 | compound_0 = FunctionExpr | Function compound | builtin-class function | +| c_tests.py:71 | x_0 = ParameterDefinition | int 1 | builtin-class int | +| c_tests.py:71 | y_0 = ParameterDefinition | int 0 | builtin-class int | +| c_tests.py:71 | y_5 = Pi(y_0) [false] | int 0 | builtin-class int | +| c_tests.py:71 | y_6 = phi(y_4, y_5) | int 0 | builtin-class int | +| c_tests.py:74 | x_2 = Pi(x_0) [true] | int 1 | builtin-class int | +| c_tests.py:76 | x_3 = SingleSuccessorGuard(x_2) [true] | int 1 | builtin-class int | +| c_tests.py:76 | y_2 = Pi(y_0) [false] | int 0 | builtin-class int | +| c_tests.py:76 | y_3 = phi(y_1, y_2) | int 0 | builtin-class int | +| c_tests.py:79 | h_0 = FunctionExpr | Function h | builtin-class function | +| c_tests.py:79 | x_4 = phi(x_2, x_3) | NoneType None | builtin-class NoneType | +| c_tests.py:80 | b_0 = IfExp | bool True | builtin-class bool | +| c_tests.py:83 | b_1 = IfExp | bool True | builtin-class bool | +| c_tests.py:87 | b_3 = Pi(b_1) [false] | bool True | builtin-class bool | +| c_tests.py:87 | b_4 = phi(b_2, b_3) | bool True | builtin-class bool | +| c_tests.py:90 | x_0 = IfExp | NoneType None | builtin-class NoneType | +| c_tests.py:94 | x_1 = IfExp | NoneType None | builtin-class NoneType | +| c_tests.py:96 | x_2 = Pi(x_1) [true] | NoneType None | builtin-class NoneType | +| c_tests.py:98 | complex_test_0 = FunctionExpr | Function complex_test | builtin-class function | +| d_globals.py:0 | D_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | Ugly_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | X_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | __name___0 = ScopeEntryDefinition | 'code.d_globals' | builtin-class str | +| d_globals.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | dict_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | g3_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | g4_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | get_g4_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | glob_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | k_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | modinit_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | outer_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | redefine_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | set_g4_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | set_g4_indirect_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | tuple_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | use_list_attribute_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | x_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | y_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:0 | z_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:2 | dict_2 = ScopeEntryDefinition | int 7 | builtin-class int | +| d_globals.py:2 | g1_2 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:2 | g2_2 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:2 | g3_2 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:2 | g4_1 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:2 | glob_2 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:2 | j_0 = FunctionExpr | Function j | builtin-class function | +| d_globals.py:2 | tuple_2 = ScopeEntryDefinition | builtin-class tuple | builtin-class type | +| d_globals.py:2 | z_2 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:5 | dict_1 = IntegerLiteral | int 7 | builtin-class int | +| d_globals.py:7 | tuple_1 = tuple | builtin-class tuple | builtin-class type | +| d_globals.py:14 | g1_0 = None | NoneType None | builtin-class NoneType | +| d_globals.py:16 | assign_global_0 = FunctionExpr | Function assign_global | builtin-class function | +| d_globals.py:16 | g2_3 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:16 | g3_3 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:16 | g4_2 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:16 | glob_3 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:16 | z_3 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:18 | g1_3 = IntegerLiteral | int 101 | builtin-class int | +| d_globals.py:23 | g2_0 = None | NoneType None | builtin-class NoneType | +| d_globals.py:25 | g1_4 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:25 | g3_4 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:25 | g3_4 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:25 | g4_3 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:25 | g4_3 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:25 | glob_4 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:25 | init_0 = FunctionExpr | Function init | builtin-class function | +| d_globals.py:25 | z_4 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:27 | g2_4 = IntegerLiteral | int 102 | builtin-class int | +| d_globals.py:29 | g1_1 = CallsiteRefinement(g1_0) | NoneType None | builtin-class NoneType | +| d_globals.py:29 | g2_1 = CallsiteRefinement(g2_0) | int 102 | builtin-class int | +| d_globals.py:29 | glob_1 = CallsiteRefinement(glob_0) | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:29 | z_1 = CallsiteRefinement(z_0) | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:33 | g3_1 = None | NoneType None | builtin-class NoneType | +| d_globals.py:35 | Ugly_1 = ClassExpr | class Ugly | builtin-class type | +| d_globals.py:37 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| d_globals.py:37 | g1_5 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:37 | g2_5 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:37 | g4_4 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:37 | glob_5 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:37 | self_0 = ParameterDefinition | self | class Ugly | +| d_globals.py:37 | z_5 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:39 | g3_5 = IntegerLiteral | int 103 | builtin-class int | +| d_globals.py:41 | g1_6 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:41 | g2_6 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:41 | g3_6 = ScopeEntryDefinition | int 103 | builtin-class int | +| d_globals.py:41 | g4_5 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:41 | glob_6 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:41 | meth_0 = FunctionExpr | Function meth | builtin-class function | +| d_globals.py:41 | self_0 = ParameterDefinition | self | class Ugly | +| d_globals.py:41 | z_6 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:46 | x_1 = IntegerLiteral | int 1 | builtin-class int | +| d_globals.py:49 | x_2 = IntegerLiteral | int 3 | builtin-class int | +| d_globals.py:51 | x_3 = phi(x_1, x_2) | int 1 | builtin-class int | +| d_globals.py:51 | x_3 = phi(x_1, x_2) | int 3 | builtin-class int | +| d_globals.py:52 | y_1 = IntegerLiteral | int 1 | builtin-class int | +| d_globals.py:54 | y_2 = IntegerLiteral | int 2 | builtin-class int | +| d_globals.py:59 | y_3 = phi(y_1, y_2) | int 1 | builtin-class int | +| d_globals.py:59 | y_3 = phi(y_1, y_2) | int 2 | builtin-class int | +| d_globals.py:62 | X_1 = ClassExpr | class X | builtin-class type | +| d_globals.py:62 | X_2 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:62 | g3_7 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:62 | y_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:62 | y_4 = ScopeEntryDefinition | int 1 | builtin-class int | +| d_globals.py:62 | y_4 = ScopeEntryDefinition | int 2 | builtin-class int | +| d_globals.py:63 | y_1 = y | int 1 | builtin-class int | +| d_globals.py:63 | y_1 = y | int 2 | builtin-class int | +| d_globals.py:70 | g1_7 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:70 | g2_7 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:70 | g3_8 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:70 | g4_7 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:70 | glob_7 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:70 | k_1 = FunctionExpr | Function k | builtin-class function | +| d_globals.py:70 | z_7 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:73 | g4_6 = None | NoneType None | builtin-class NoneType | +| d_globals.py:75 | g1_8 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:75 | g2_8 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:75 | g3_9 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:75 | g4_8 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:75 | get_g4_1 = FunctionExpr | Function get_g4 | builtin-class function | +| d_globals.py:75 | glob_8 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:75 | set_g4_2 = ScopeEntryDefinition | Function set_g4 | builtin-class function | +| d_globals.py:75 | z_8 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:77 | g1_9 = CallsiteRefinement(g1_8) | NoneType None | builtin-class NoneType | +| d_globals.py:77 | g2_9 = CallsiteRefinement(g2_8) | int 102 | builtin-class int | +| d_globals.py:77 | g3_10 = CallsiteRefinement(g3_9) | NoneType None | builtin-class NoneType | +| d_globals.py:77 | g4_9 = Pi(g4_8) [true] | NoneType None | builtin-class NoneType | +| d_globals.py:77 | g4_10 = CallsiteRefinement(g4_9) | bool False | builtin-class bool | +| d_globals.py:77 | glob_9 = CallsiteRefinement(glob_8) | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:77 | z_9 = CallsiteRefinement(z_8) | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:78 | g1_10 = phi(g1_8, g1_9) | NoneType None | builtin-class NoneType | +| d_globals.py:78 | g2_10 = phi(g2_8, g2_9) | int 102 | builtin-class int | +| d_globals.py:78 | g3_11 = phi(g3_9, g3_10) | NoneType None | builtin-class NoneType | +| d_globals.py:78 | g4_12 = phi(g4_10, g4_11) | bool False | builtin-class bool | +| d_globals.py:78 | glob_10 = phi(glob_8, glob_9) | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:78 | z_10 = phi(z_8, z_9) | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:80 | g1_11 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:80 | g2_11 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:80 | g3_12 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:80 | g4_13 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:80 | glob_11 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:80 | set_g4_1 = FunctionExpr | Function set_g4 | builtin-class function | +| d_globals.py:80 | set_g4_indirect_2 = ScopeEntryDefinition | Function set_g4_indirect | builtin-class function | +| d_globals.py:80 | z_11 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:81 | g1_12 = CallsiteRefinement(g1_11) | NoneType None | builtin-class NoneType | +| d_globals.py:81 | g2_12 = CallsiteRefinement(g2_11) | int 102 | builtin-class int | +| d_globals.py:81 | g3_13 = CallsiteRefinement(g3_12) | NoneType None | builtin-class NoneType | +| d_globals.py:81 | g4_14 = CallsiteRefinement(g4_13) | bool False | builtin-class bool | +| d_globals.py:81 | glob_12 = CallsiteRefinement(glob_11) | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:81 | z_12 = CallsiteRefinement(z_11) | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:83 | g1_13 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:83 | g2_13 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:83 | g3_14 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:83 | glob_13 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:83 | set_g4_indirect_1 = FunctionExpr | Function set_g4_indirect | builtin-class function | +| d_globals.py:83 | z_13 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:85 | g4_15 = False | bool False | builtin-class bool | +| d_globals.py:87 | modinit_1 = ClassExpr | class modinit | builtin-class type | +| d_globals.py:92 | modinit_2 = DeletionDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:95 | g1_14 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:95 | g2_14 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:95 | g3_15 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:95 | g4_16 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:95 | glob_14 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:95 | outer_1 = FunctionExpr | Function outer | builtin-class function | +| d_globals.py:95 | z_14 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:96 | g1_16 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:96 | g2_16 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:96 | g3_17 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:96 | g4_18 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:96 | inner_0 = FunctionExpr | Function inner | builtin-class function | +| d_globals.py:96 | z_16 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:98 | glob_16 = IntegerLiteral | int 100 | builtin-class int | +| d_globals.py:101 | g1_17 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:101 | g2_17 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:101 | g3_18 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:101 | g4_19 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:101 | glob_17 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:101 | otherInner_0 = FunctionExpr | Function otherInner | builtin-class function | +| d_globals.py:101 | z_17 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:104 | g1_15 = CallsiteRefinement(g1_14) | NoneType None | builtin-class NoneType | +| d_globals.py:104 | g2_15 = CallsiteRefinement(g2_14) | int 102 | builtin-class int | +| d_globals.py:104 | g3_16 = CallsiteRefinement(g3_15) | NoneType None | builtin-class NoneType | +| d_globals.py:104 | g4_17 = CallsiteRefinement(g4_16) | NoneType None | builtin-class NoneType | +| d_globals.py:104 | glob_15 = CallsiteRefinement(glob_14) | int 100 | builtin-class int | +| d_globals.py:104 | z_15 = CallsiteRefinement(z_14) | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:107 | g1_18 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:107 | g2_18 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:107 | g3_19 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:107 | g4_20 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:107 | glob_18 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:107 | redefine_1 = FunctionExpr | Function redefine | builtin-class function | +| d_globals.py:107 | z_18 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:110 | z_19 = IntegerLiteral | int 1 | builtin-class int | +| d_globals.py:113 | glob_19 = IntegerLiteral | int 50 | builtin-class int | +| d_globals.py:118 | D_1 = ClassExpr | class D | builtin-class type | +| d_globals.py:120 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| d_globals.py:120 | g1_19 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:120 | g2_19 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:120 | g3_20 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:120 | g4_21 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:120 | glob_20 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:120 | self_0 = ParameterDefinition | self | class D | +| d_globals.py:120 | z_20 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:123 | dict_3 = ScopeEntryDefinition | int 7 | builtin-class int | +| d_globals.py:123 | foo_0 = FunctionExpr | Function foo | builtin-class function | +| d_globals.py:123 | g1_20 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:123 | g2_20 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:123 | g3_21 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:123 | g4_22 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:123 | glob_21 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:123 | self_0 = ParameterDefinition | self | class D | +| d_globals.py:123 | z_21 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:126 | g1_21 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:126 | g2_21 = ScopeEntryDefinition | int 102 | builtin-class int | +| d_globals.py:126 | g3_22 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:126 | g4_23 = ScopeEntryDefinition | NoneType None | builtin-class NoneType | +| d_globals.py:126 | glob_22 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:126 | use_list_attribute_1 = FunctionExpr | Function use_list_attribute | builtin-class function | +| d_globals.py:126 | z_22 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:127 | l_0 = List | List | builtin-class list | +| d_globals.py:128 | g1_22 = CallsiteRefinement(g1_21) | NoneType None | builtin-class NoneType | +| d_globals.py:128 | g2_22 = CallsiteRefinement(g2_21) | int 102 | builtin-class int | +| d_globals.py:128 | g3_23 = CallsiteRefinement(g3_22) | NoneType None | builtin-class NoneType | +| d_globals.py:128 | g4_24 = CallsiteRefinement(g4_23) | NoneType None | builtin-class NoneType | +| d_globals.py:128 | glob_23 = CallsiteRefinement(glob_22) | *UNDEFINED* | *UNKNOWN TYPE* | +| d_globals.py:128 | l_1 = ArgumentRefinement(l_0) | List | builtin-class list | +| d_globals.py:128 | z_23 = CallsiteRefinement(z_22) | *UNDEFINED* | *UNKNOWN TYPE* | +| e_temporal.py:0 | __name___0 = ScopeEntryDefinition | 'code.e_temporal' | builtin-class str | +| e_temporal.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| e_temporal.py:0 | x_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| e_temporal.py:2 | sys_0 = ImportExpr | Module sys | builtin-class module | +| e_temporal.py:4 | f_0 = FunctionExpr | Function f | builtin-class function | +| e_temporal.py:4 | sys_1 = ScopeEntryDefinition | Module sys | builtin-class module | +| e_temporal.py:9 | arg_0 = ParameterDefinition | int 1 | builtin-class int | +| e_temporal.py:9 | g_0 = FunctionExpr | Function g | builtin-class function | +| e_temporal.py:12 | x_1 = g() | int 1 | builtin-class int | +| g_class_init.py:0 | __name___0 = ScopeEntryDefinition | 'code.g_class_init' | builtin-class str | +| g_class_init.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| g_class_init.py:3 | C_0 = ClassExpr | class C | builtin-class type | +| g_class_init.py:5 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| g_class_init.py:5 | self_0 = ParameterDefinition | self | class C | +| g_class_init.py:6 | self_1 = SelfCallsiteRefinement(self_0) | self | class C | +| g_class_init.py:7 | self_2 = AttributeAssignment 'x'(self_1) | self | class C | +| g_class_init.py:9 | _init_0 = FunctionExpr | Function _init | builtin-class function | +| g_class_init.py:9 | self_0 = ParameterDefinition | self | class C | +| g_class_init.py:10 | self_1 = AttributeAssignment 'y'(self_0) | self | class C | +| g_class_init.py:11 | self_2 = SelfCallsiteRefinement(self_1) | self | class C | +| g_class_init.py:13 | _init2_0 = FunctionExpr | Function _init2 | builtin-class function | +| g_class_init.py:13 | self_0 = ParameterDefinition | self | class C | +| g_class_init.py:14 | self_1 = AttributeAssignment 'z'(self_0) | self | class C | +| g_class_init.py:16 | method_0 = FunctionExpr | Function method | builtin-class function | +| g_class_init.py:16 | self_0 = ParameterDefinition | self | class C | +| g_class_init.py:19 | self_1 = Pi(self_0) [true] | self | class C | +| g_class_init.py:20 | self_2 = Pi(self_0) [false] | self | class C | +| g_class_init.py:20 | self_3 = phi(self_1, self_2) | self | class C | +| g_class_init.py:24 | Oddities_0 = ClassExpr | class Oddities | builtin-class type | +| g_class_init.py:24 | float_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| g_class_init.py:24 | int_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| g_class_init.py:26 | int_1 = int | builtin-class int | builtin-class type | +| g_class_init.py:27 | float_1 = float | builtin-class float | builtin-class type | +| g_class_init.py:28 | l_0 = len | Builtin-function len | builtin-class builtin_function_or_method | +| g_class_init.py:29 | h_0 = hash | Builtin-function hash | builtin-class builtin_function_or_method | +| g_class_init.py:32 | D_0 = ClassExpr | class D | builtin-class type | +| g_class_init.py:34 | D_1 = ScopeEntryDefinition | class D | builtin-class type | +| g_class_init.py:34 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| g_class_init.py:34 | self_0 = ParameterDefinition | self | class D | +| g_class_init.py:35 | D_2 = ArgumentRefinement(D_1) | class D | builtin-class type | +| g_class_init.py:42 | V2_0 = Str | 'v2' | builtin-class str | +| g_class_init.py:43 | V3_0 = Str | 'v3' | builtin-class str | +| g_class_init.py:45 | E_0 = ClassExpr | class E | builtin-class type | +| g_class_init.py:46 | V2_1 = ScopeEntryDefinition | 'v2' | builtin-class str | +| g_class_init.py:46 | V3_1 = ScopeEntryDefinition | 'v3' | builtin-class str | +| g_class_init.py:46 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| g_class_init.py:46 | self_0 = ParameterDefinition | self | class E | +| g_class_init.py:46 | self_3 = phi(self_1, self_2) | self | class E | +| g_class_init.py:48 | self_1 = AttributeAssignment 'version'(self_0) | self | class E | +| g_class_init.py:50 | self_2 = AttributeAssignment 'version'(self_0) | self | class E | +| g_class_init.py:52 | V2_2 = ScopeEntryDefinition | 'v2' | builtin-class str | +| g_class_init.py:52 | meth_0 = FunctionExpr | Function meth | builtin-class function | +| g_class_init.py:52 | self_0 = ParameterDefinition | self | class E | +| g_class_init.py:52 | self_2 = Pi(self_0) [false] | self | class E | +| g_class_init.py:52 | self_3 = phi(self_1, self_2) | self | class E | +| g_class_init.py:54 | self_1 = Pi(self_0) [true] | self | class E | +| j_convoluted_imports.py:0 | __name___0 = ScopeEntryDefinition | 'code.j_convoluted_imports' | builtin-class str | +| j_convoluted_imports.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| j_convoluted_imports.py:3 | module_0 = ImportMember | Function module | builtin-class function | +| j_convoluted_imports.py:6 | x_0 = ImportMember | Module code.package.x | builtin-class module | +| j_convoluted_imports.py:9 | C_0 = ClassExpr | class C | builtin-class type | +| j_convoluted_imports.py:11 | module2_0 = ImportMember | int 7 | builtin-class int | +| j_convoluted_imports.py:13 | f_0 = FunctionExpr | Function f | builtin-class function | +| j_convoluted_imports.py:13 | self_0 = ParameterDefinition | self | class C | +| j_convoluted_imports.py:14 | x_0 = ImportMember | Module code.package.x | builtin-class module | +| j_convoluted_imports.py:16 | moduleX_0 = ImportMember | Module code.package.moduleX | builtin-class module | +| m_attributes.py:0 | __name___0 = ScopeEntryDefinition | 'code.m_attributes' | builtin-class str | +| m_attributes.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| m_attributes.py:3 | C_0 = ClassExpr | class C | builtin-class type | +| m_attributes.py:5 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| m_attributes.py:5 | a_0 = ParameterDefinition | int 17 | builtin-class int | +| m_attributes.py:5 | a_0 = ParameterDefinition | int 100 | builtin-class int | +| m_attributes.py:5 | self_0 = ParameterDefinition | self | class C | +| m_attributes.py:6 | self_1 = AttributeAssignment 'a'(self_0) | self | class C | +| m_attributes.py:8 | foo_0 = FunctionExpr | Function foo | builtin-class function | +| m_attributes.py:8 | other_0 = ParameterDefinition | C() | class C | +| m_attributes.py:8 | self_0 = ParameterDefinition | self | class C | +| n_nesting.py:0 | D_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| n_nesting.py:0 | __name___0 = ScopeEntryDefinition | 'code.n_nesting' | builtin-class str | +| n_nesting.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| n_nesting.py:8 | C_0 = ScopeEntryDefinition | int 1 | builtin-class int | +| n_nesting.py:8 | compile_ops_0 = ParameterDefinition | bool True | builtin-class bool | +| n_nesting.py:8 | foo_0 = FunctionExpr | Function foo | builtin-class function | +| n_nesting.py:9 | C_1 = CallsiteRefinement(C_0) | int 1 | builtin-class int | +| n_nesting.py:9 | compile_ops_1 = ArgumentRefinement(compile_ops_0) | bool True | builtin-class bool | +| n_nesting.py:10 | C_5 = ScopeEntryDefinition | int 1 | builtin-class int | +| n_nesting.py:10 | compile_ops_3 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| n_nesting.py:10 | inner_0 = FunctionExpr | Function inner | builtin-class function | +| n_nesting.py:13 | C_7 = ScopeEntryDefinition | int 1 | builtin-class int | +| n_nesting.py:13 | compile_ops_4 = Pi(compile_ops_1) [false] | bool True | builtin-class bool | +| n_nesting.py:13 | compile_ops_5 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| n_nesting.py:13 | inner_1 = FunctionExpr | Function inner | builtin-class function | +| n_nesting.py:15 | attrs_0 = Dict | Dict | builtin-class dict | +| n_nesting.py:16 | compile_ops_6 = phi(compile_ops_2, compile_ops_4) | bool True | builtin-class bool | +| n_nesting.py:16 | inner_2 = phi(inner_0, inner_1) | Function inner | builtin-class function | +| n_nesting.py:22 | C_9 = ScopeEntryDefinition | int 1 | builtin-class int | +| n_nesting.py:22 | f1_0 = FunctionExpr | Function f1 | builtin-class function | +| n_nesting.py:23 | C_10 = AttributeAssignment 'flag'(C_9) | int 1 | builtin-class int | +| n_nesting.py:24 | C_11 = ScopeEntryDefinition | class C | builtin-class type | +| n_nesting.py:24 | C_11 = ScopeEntryDefinition | int 1 | builtin-class int | +| n_nesting.py:24 | f1_1 = ScopeEntryDefinition | Function f1 | builtin-class function | +| n_nesting.py:24 | f2_0 = FunctionExpr | Function f2 | builtin-class function | +| n_nesting.py:25 | C_12 = CallsiteRefinement(C_11) | class C | builtin-class type | +| n_nesting.py:25 | C_12 = CallsiteRefinement(C_11) | int 1 | builtin-class int | +| n_nesting.py:26 | C_13 = ScopeEntryDefinition | class C | builtin-class type | +| n_nesting.py:26 | C_13 = ScopeEntryDefinition | int 1 | builtin-class int | +| n_nesting.py:26 | f2_1 = ScopeEntryDefinition | Function f2 | builtin-class function | +| n_nesting.py:26 | f3_0 = FunctionExpr | Function f3 | builtin-class function | +| n_nesting.py:27 | C_14 = CallsiteRefinement(C_13) | class C | builtin-class type | +| n_nesting.py:27 | C_14 = CallsiteRefinement(C_13) | int 1 | builtin-class int | +| n_nesting.py:28 | C_15 = ScopeEntryDefinition | class C | builtin-class type | +| n_nesting.py:28 | C_15 = ScopeEntryDefinition | int 1 | builtin-class int | +| n_nesting.py:28 | f3_1 = ScopeEntryDefinition | Function f3 | builtin-class function | +| n_nesting.py:28 | f4_0 = FunctionExpr | Function f4 | builtin-class function | +| n_nesting.py:29 | C_16 = CallsiteRefinement(C_15) | class C | builtin-class type | +| n_nesting.py:29 | C_16 = CallsiteRefinement(C_15) | int 1 | builtin-class int | +| n_nesting.py:30 | C_2 = ClassExpr | class C | builtin-class type | +| n_nesting.py:31 | C_3 = CallsiteRefinement(C_2) | class C | builtin-class type | +| n_nesting.py:32 | D_1 = ClassExpr | class D | builtin-class type | +| n_nesting.py:34 | C_4 = IntegerLiteral | int 1 | builtin-class int | +| q_super.py:0 | __name___0 = ScopeEntryDefinition | 'code.q_super' | builtin-class str | +| q_super.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| q_super.py:1 | Base2_0 = ClassExpr | class Base2 | builtin-class type | +| q_super.py:3 | Base2_1 = ScopeEntryDefinition | class Base2 | builtin-class type | +| q_super.py:3 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| q_super.py:3 | self_0 = ParameterDefinition | self | class Base2 | +| q_super.py:3 | self_0 = ParameterDefinition | self | class Derived4 | +| q_super.py:8 | Derived4_0 = ClassExpr | class Derived4 | builtin-class type | +| q_super.py:10 | Base2_2 = ScopeEntryDefinition | class Base2 | builtin-class type | +| q_super.py:10 | Derived4_1 = ScopeEntryDefinition | class Derived4 | builtin-class type | +| q_super.py:10 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| q_super.py:10 | self_0 = ParameterDefinition | self | class Derived4 | +| q_super.py:14 | Base1_0 = ClassExpr | class Base1 | builtin-class type | +| q_super.py:16 | meth_0 = FunctionExpr | Function meth | builtin-class function | +| q_super.py:16 | self_0 = ParameterDefinition | self | class Base1 | +| q_super.py:16 | self_0 = ParameterDefinition | self | class Derived1 | +| q_super.py:16 | self_0 = ParameterDefinition | self | class Derived2 | +| q_super.py:16 | self_0 = ParameterDefinition | self | class Derived5 | +| q_super.py:16 | self_0 = ParameterDefinition | self | class Wrong1 | +| q_super.py:19 | Derived1_0 = ClassExpr | class Derived1 | builtin-class type | +| q_super.py:21 | Derived1_1 = ScopeEntryDefinition | class Derived1 | builtin-class type | +| q_super.py:21 | meth_0 = FunctionExpr | Function meth | builtin-class function | +| q_super.py:21 | self_0 = ParameterDefinition | self | class Derived1 | +| q_super.py:21 | self_0 = ParameterDefinition | self | class Derived2 | +| q_super.py:21 | self_0 = ParameterDefinition | self | class Derived5 | +| q_super.py:21 | self_0 = ParameterDefinition | self | class Wrong1 | +| q_super.py:24 | Derived2_0 = ClassExpr | class Derived2 | builtin-class type | +| q_super.py:26 | Derived2_1 = ScopeEntryDefinition | class Derived2 | builtin-class type | +| q_super.py:26 | meth_0 = FunctionExpr | Function meth | builtin-class function | +| q_super.py:26 | self_0 = ParameterDefinition | self | class Derived2 | +| q_super.py:26 | self_0 = ParameterDefinition | self | class Wrong1 | +| q_super.py:29 | Derived5_0 = ClassExpr | class Derived5 | builtin-class type | +| q_super.py:31 | Derived5_1 = ScopeEntryDefinition | class Derived5 | builtin-class type | +| q_super.py:31 | meth_0 = FunctionExpr | Function meth | builtin-class function | +| q_super.py:31 | self_0 = ParameterDefinition | self | class Derived5 | +| q_super.py:35 | Wrong1_0 = ClassExpr | class Wrong1 | builtin-class type | +| q_super.py:37 | Derived5_2 = ScopeEntryDefinition | class Derived5 | builtin-class type | +| q_super.py:37 | meth_0 = FunctionExpr | Function meth | builtin-class function | +| q_super.py:37 | self_0 = ParameterDefinition | self | class Wrong1 | +| q_super.py:41 | DA_0 = ClassExpr | class DA | builtin-class type | +| q_super.py:43 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| q_super.py:43 | self_0 = ParameterDefinition | self | class DA | +| q_super.py:43 | self_0 = ParameterDefinition | self | class DC | +| q_super.py:43 | self_0 = ParameterDefinition | self | class DD | +| q_super.py:43 | self_0 = ParameterDefinition | self | class DF | +| q_super.py:46 | DA_1 = ScopeEntryDefinition | class DA | builtin-class type | +| q_super.py:46 | DB_0 = ClassExpr | class DB | builtin-class type | +| q_super.py:48 | DC_0 = ClassExpr | class DC | builtin-class type | +| q_super.py:50 | DB_1 = ScopeEntryDefinition | class DB | builtin-class type | +| q_super.py:50 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| q_super.py:50 | self_0 = ParameterDefinition | self | class DC | +| q_super.py:51 | sup_0 = super() | super() | builtin-class super | +| q_super.py:52 | sup_1 = MethodCallsiteRefinement(sup_0) | super() | builtin-class super | +| q_super.py:55 | DD_0 = ClassExpr | class DD | builtin-class type | +| q_super.py:57 | DD_1 = ScopeEntryDefinition | class DD | builtin-class type | +| q_super.py:57 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| q_super.py:57 | self_0 = ParameterDefinition | self | class DD | +| q_super.py:58 | sup_0 = super() | super() | builtin-class super | +| q_super.py:59 | sup_1 = MethodCallsiteRefinement(sup_0) | super() | builtin-class super | +| q_super.py:61 | DA_2 = ScopeEntryDefinition | class DA | builtin-class type | +| q_super.py:61 | DE_0 = ClassExpr | class DE | builtin-class type | +| q_super.py:63 | DF_0 = ClassExpr | class DF | builtin-class type | +| q_super.py:65 | DE_1 = ScopeEntryDefinition | class DE | builtin-class type | +| q_super.py:65 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| q_super.py:65 | self_0 = ParameterDefinition | self | class DF | +| q_super.py:68 | N_0 = ClassExpr | class N | builtin-class type | +| q_super.py:71 | M_0 = ClassExpr | class M | builtin-class type | +| q_super.py:73 | M_1 = ScopeEntryDefinition | class M | builtin-class type | +| q_super.py:73 | __init___0 = FunctionExpr | Function __init__ | builtin-class function | +| q_super.py:73 | self_0 = ParameterDefinition | self | class M | +| q_super.py:74 | s_0 = super() | super() | builtin-class super | +| q_super.py:75 | i_0 = Attribute | super().__init__ | builtin-class method | +| s_scopes.py:0 | __name___0 = ScopeEntryDefinition | 'code.s_scopes' | builtin-class str | +| s_scopes.py:0 | __package___0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| s_scopes.py:0 | float_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| s_scopes.py:0 | x_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| s_scopes.py:4 | float_1 = True | bool True | builtin-class bool | +| s_scopes.py:5 | float_2 = phi(float_0, float_1) | *UNDEFINED* | *UNKNOWN TYPE* | +| s_scopes.py:5 | float_2 = phi(float_0, float_1) | bool True | builtin-class bool | +| s_scopes.py:7 | C2_0 = ClassExpr | class C2 | builtin-class type | +| s_scopes.py:7 | float_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| s_scopes.py:7 | float_3 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| s_scopes.py:7 | float_3 = ScopeEntryDefinition | bool True | builtin-class bool | +| s_scopes.py:7 | int_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| s_scopes.py:7 | str_0 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | +| s_scopes.py:9 | i1_0 = int | builtin-class int | builtin-class type | +| s_scopes.py:10 | f1_0 = float | bool True | builtin-class bool | +| s_scopes.py:10 | f1_0 = float | builtin-class float | builtin-class type | +| s_scopes.py:12 | int_1 = IntegerLiteral | int 0 | builtin-class int | +| s_scopes.py:15 | str_1 = FloatLiteral | float 1.0 | builtin-class float | +| s_scopes.py:17 | float_1 = None | NoneType None | builtin-class NoneType | +| s_scopes.py:18 | float_2 = phi(float_0, float_1) | *UNDEFINED* | *UNKNOWN TYPE* | +| s_scopes.py:18 | float_2 = phi(float_0, float_1) | NoneType None | builtin-class NoneType | +| s_scopes.py:18 | i2_0 = int | int 0 | builtin-class int | +| s_scopes.py:18 | str_2 = phi(str_0, str_1) | *UNDEFINED* | *UNKNOWN TYPE* | +| s_scopes.py:18 | str_2 = phi(str_0, str_1) | float 1.0 | builtin-class float | +| s_scopes.py:19 | s_0 = str | builtin-class str | builtin-class type | +| s_scopes.py:19 | s_0 = str | float 1.0 | builtin-class float | +| s_scopes.py:20 | f2_0 = float | NoneType None | builtin-class NoneType | +| s_scopes.py:20 | f2_0 = float | bool True | builtin-class bool | +| s_scopes.py:20 | f2_0 = float | builtin-class float | builtin-class type | +| s_scopes.py:23 | i_0 = int | builtin-class int | builtin-class type | +| s_scopes.py:24 | f_0 = float | bool True | builtin-class bool | +| s_scopes.py:24 | f_0 = float | builtin-class float | builtin-class type | diff --git a/python/ql/test/library-tests/PointsTo/new/SSA.ql b/python/ql/test/library-tests/PointsTo/new/SSA.ql new file mode 100644 index 000000000000..e9ed68645677 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/SSA.ql @@ -0,0 +1,10 @@ + +import python +private import semmle.python.pointsto.PointsTo +private import semmle.python.pointsto.PointsToContext +import Util + +from EssaVariable v, EssaDefinition def, Object o, ClassObject cls +where def = v.getDefinition() and +PointsTo::ssa_variable_points_to(v, _, o, cls, _) +select locate(def.getLocation(), "abcdegjqmns_"), v.getRepresentation() + " = " + def.getRepresentation(), repr(o), repr(cls) diff --git a/python/ql/test/library-tests/PointsTo/new/Sanity.expected b/python/ql/test/library-tests/PointsTo/new/Sanity.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/PointsTo/new/Sanity.ql b/python/ql/test/library-tests/PointsTo/new/Sanity.ql new file mode 100644 index 000000000000..94c7dfa18158 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Sanity.ql @@ -0,0 +1,121 @@ + +import python +import semmle.python.pointsto.PointsTo + +predicate ssa_sanity(string clsname, string problem, string what) { + /* Exactly one definition of each SSA variable */ + exists(EssaVariable var | + clsname = var.getAQlClass() | + /* Exactly one definition of each SSA variable */ + count(var.getDefinition()) != 1 and problem = " has " + count(var.getDefinition()) + " definitions." and + what = "SSA variable " + var.getSourceVariable().getName() + or + /* Backing variable */ + not exists(var.getSourceVariable()) and problem = "An SSA variable has no backing variable." and + what = "An SSA variable" + or + count(var.getSourceVariable()) != 1 and problem = var.getSourceVariable().getName() + " has " + count(var.getSourceVariable()) + " backing variables." and + what = "SSA variable " + var.getSourceVariable().getName() + ) + or + /* Exactly one location */ + exists(EssaDefinition def | + clsname = def.getAQlClass() and + what = "SSA Definition " + def.getSourceVariable().getName() + " in " + def.getSourceVariable().(Variable).getScope().getName() and + count(def.getLocation()) != 1 and problem = " has " + count(def.getLocation()) + " locations" + ) + or + /* Must have a source variable */ + exists(EssaDefinition def | + clsname = def.getAQlClass() and + not exists(def.getSourceVariable()) and + what = " at " + def.getLocation() and + problem = "has not source variable" + ) + or + /* Variables must have exactly one representation */ + exists(EssaVariable var | + clsname = var.getAQlClass() and + what = "SSA variable " + var.getSourceVariable().getName() + " defined at " + var.getDefinition().getLocation() and + count(var.getRepresentation()) != 1 and problem = " has " + count(var.getRepresentation()) + " representations" + ) + or + /* Definitions must have exactly one representation */ + exists(EssaDefinition def | + clsname = def.getAQlClass() and + what = "SSA definition " + def.getSourceVariable().getName() + " at " + def.getLocation() and + count(def.getRepresentation()) != 1 and problem = " has " + count(def.getRepresentation()) + " representations: " + def.getRepresentation() + ) + or + /* Refinements must have exactly one input */ + exists(EssaNodeRefinement ref | + clsname = ref.getAQlClass() and + what = "Refinement " + ref.getSourceVariable().getName() + " at " + ref.getLocation() and + count(ref.getInput()) != 1 and problem = " has " + count(ref.getInput()) + " inputs: " + ref.getInput().getRepresentation() + ) + or + /* Ideally filter nodes should have exactly one input, but it is not a big deal + * if we prune away the input, leaving it with none. */ + exists(EssaEdgeRefinement def | + clsname = def.getAQlClass() and + what = def.getSourceVariable().getName() + " at " + def.getLocation() | + count(def.getInput()) > 1 and problem =" has " + count(def.getInput()) + " inputs." + ) + or + /* Each use has only one reaching SSA variable */ + exists(ControlFlowNode use, SsaSourceVariable v, int c | + c = strictcount(EssaVariable s | s.getAUse() = use and s.getSourceVariable() = v) and + clsname = use.getAQlClass() and c != 1 and + what = use + " at " + use.getLocation() and + problem =" has " + c + " SSA variables reaching." + ) + or + /* Python-specific subclasses of EssaDefinitions should be disjoint and complete */ + exists(EssaDefinition def | + clsname = def.getAQlClass() and + what = def.getVariable().getName() + " at " + def.getLocation() and + problem = "has non-disjoint subclasses" | + strictcount(def.getAQlClass()) > 2 or + /* OK if method call and argument overlap: `x.foo(x)` */ + strictcount(def.getAQlClass()) > 1 and + not clsname = "ArgumentRefinement" and not clsname = "SelfCallsiteRefinement" + ) + or + exists(EssaDefinition def | + clsname = def.getAQlClass() and + clsname.prefix(4) = "Essa" and + what = " at " + def.getLocation() and + problem = "not covered by Python-specific subclass." + ) + or + // All modules should have __name__ + exists(Module m | + what = " at " + m.getLocation() and + clsname = "Module" | + not exists(m.getName()) and + problem = "does not have a name" + or + not exists(Variable v | v.getId() = "__name__" and v.getScope() = m) and + problem = "does not have a __name__ variable" + or + not exists(PyNodeDefinition def | + def.getDefiningNode().getScope() = m and + def.getVariable().getName() = "__name__" + ) and + problem = "does not have an ImplicitModuleNameDefinition" + ) + or + // Unknown value should always have the class unknownType + exists(ControlFlowNode f, ClassObject cls | + PointsTo::points_to(f, _, unknownValue(), cls, _) and + clsname = f.getAQlClass() and + cls != theUnknownType() and + problem = "unknownValue() has class != theUnknownType()" and + what = cls.getName() + ) +} + +from string clsname, string problem, string what +where ssa_sanity(clsname, problem, what) +select clsname, what, problem + diff --git a/python/ql/test/library-tests/PointsTo/new/SourceEdgeDefinitions.expected b/python/ql/test/library-tests/PointsTo/new/SourceEdgeDefinitions.expected new file mode 100644 index 000000000000..900b5ee91527 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/SourceEdgeDefinitions.expected @@ -0,0 +1,22 @@ +| b_condition.py:7 | Local Variable x | ControlFlowNode for x | +| b_condition.py:13 | Local Variable x | ControlFlowNode for x | +| b_condition.py:19 | Local Variable x | ControlFlowNode for x | +| b_condition.py:25 | Local Variable x | ControlFlowNode for x | +| b_condition.py:32 | Local Variable x | ControlFlowNode for x | +| b_condition.py:36 | Local Variable x | ControlFlowNode for x | +| b_condition.py:42 | Global Variable v2 | ControlFlowNode for v2 | +| b_condition.py:51 | Local Variable x | ControlFlowNode for x | +| b_condition.py:57 | Local Variable v | ControlFlowNode for v | +| b_condition.py:62 | Local Variable x | ControlFlowNode for x | +| b_condition.py:64 | Local Variable y | ControlFlowNode for y | +| b_condition.py:65 | Local Variable x | ControlFlowNode for x | +| b_condition.py:66 | Local Variable x | ControlFlowNode for x | +| b_condition.py:71 | Local Variable b | ControlFlowNode for b | +| b_condition.py:77 | Local Variable t | ControlFlowNode for t | +| b_condition.py:82 | Local Variable foo | ControlFlowNode for foo | +| b_condition.py:88 | Local Variable x | ControlFlowNode for x | +| b_condition.py:88 | Local Variable y | ControlFlowNode for y | +| b_condition.py:90 | Local Variable y | ControlFlowNode for y | +| b_condition.py:102 | Local Variable a | ControlFlowNode for a | +| b_condition.py:104 | Local Variable a | ControlFlowNode for a | +| b_condition.py:105 | Local Variable a | ControlFlowNode for a | diff --git a/python/ql/test/library-tests/PointsTo/new/SourceEdgeDefinitions.ql b/python/ql/test/library-tests/PointsTo/new/SourceEdgeDefinitions.ql new file mode 100644 index 000000000000..4feb22b31ba6 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/SourceEdgeDefinitions.ql @@ -0,0 +1,10 @@ + +import python +import semmle.dataflow.SSA +import semmle.python.pointsto.PointsTo + +import Util + +from SsaSourceVariable var, ControlFlowNode use, BasicBlock pred +where var.hasRefinementEdge(use, pred, _) +select locate(pred.getLastNode().getLocation(), "ab"), var.(Variable), use.toString() diff --git a/python/ql/test/library-tests/PointsTo/new/SourceNodeDefinitions.expected b/python/ql/test/library-tests/PointsTo/new/SourceNodeDefinitions.expected new file mode 100644 index 000000000000..55363237c62f --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/SourceNodeDefinitions.expected @@ -0,0 +1,121 @@ +| a_simple.py:0 | Global Variable C | Entry node for Module code.a_simple | definition | +| a_simple.py:0 | Global Variable __name__ | Entry node for Module code.a_simple | definition | +| a_simple.py:0 | Global Variable __package__ | Entry node for Module code.a_simple | definition | +| a_simple.py:0 | Global Variable f | Entry node for Module code.a_simple | definition | +| a_simple.py:0 | Global Variable f1 | Entry node for Module code.a_simple | definition | +| a_simple.py:0 | Global Variable func | Entry node for Module code.a_simple | definition | +| a_simple.py:0 | Global Variable i1 | Entry node for Module code.a_simple | definition | +| a_simple.py:0 | Global Variable multi_loop | Entry node for Module code.a_simple | definition | +| a_simple.py:0 | Global Variable multi_loop_in_try | Entry node for Module code.a_simple | definition | +| a_simple.py:0 | Global Variable s | Entry node for Module code.a_simple | definition | +| a_simple.py:0 | Global Variable vararg_kwarg | Entry node for Module code.a_simple | definition | +| a_simple.py:0 | Global Variable with_definition | Entry node for Module code.a_simple | definition | +| a_simple.py:2 | Global Variable f1 | ControlFlowNode for f1 | definition | +| a_simple.py:5 | Global Variable i1 | ControlFlowNode for i1 | definition | +| a_simple.py:6 | Global Variable s | ControlFlowNode for s | definition | +| a_simple.py:8 | Global Variable func | ControlFlowNode for func | definition | +| a_simple.py:11 | Global Variable C | ControlFlowNode for C | definition | +| a_simple.py:14 | Global Variable vararg_kwarg | ControlFlowNode for vararg_kwarg | definition | +| a_simple.py:14 | Local Variable d | ControlFlowNode for d | definition | +| a_simple.py:14 | Local Variable d | Entry node for Function vararg_kwarg | definition | +| a_simple.py:14 | Local Variable t | ControlFlowNode for t | definition | +| a_simple.py:14 | Local Variable t | Entry node for Function vararg_kwarg | definition | +| a_simple.py:18 | Global Variable multi_loop | ControlFlowNode for multi_loop | definition | +| a_simple.py:18 | Local Variable seq | ControlFlowNode for seq | definition | +| a_simple.py:18 | Local Variable x | Entry node for Function multi_loop | definition | +| a_simple.py:18 | Local Variable y | Entry node for Function multi_loop | definition | +| a_simple.py:19 | Local Variable x | ControlFlowNode for x | definition | +| a_simple.py:20 | Local Variable x | ControlFlowNode for x | definition | +| a_simple.py:20 | Local Variable y | ControlFlowNode for y | definition | +| a_simple.py:23 | Global Variable with_definition | ControlFlowNode for with_definition | definition | +| a_simple.py:23 | Local Variable x | ControlFlowNode for x | definition | +| a_simple.py:23 | Local Variable y | Entry node for Function with_definition | definition | +| a_simple.py:24 | Local Variable y | ControlFlowNode for y | definition | +| a_simple.py:27 | Global Variable multi_loop_in_try | ControlFlowNode for multi_loop_in_try | definition | +| a_simple.py:27 | Local Variable p | Entry node for Function multi_loop_in_try | definition | +| a_simple.py:27 | Local Variable q | Entry node for Function multi_loop_in_try | definition | +| a_simple.py:27 | Local Variable x | ControlFlowNode for x | definition | +| a_simple.py:29 | Local Variable p | ControlFlowNode for p | definition | +| a_simple.py:29 | Local Variable q | ControlFlowNode for q | definition | +| a_simple.py:34 | Global Variable f | ControlFlowNode for f | definition | +| a_simple.py:34 | Local Variable args | ControlFlowNode for args | definition | +| a_simple.py:34 | Local Variable args | Entry node for Function f | definition | +| a_simple.py:34 | Local Variable kwargs | ControlFlowNode for kwargs | definition | +| a_simple.py:34 | Local Variable kwargs | Entry node for Function f | definition | +| b_condition.py:0 | Global Variable __name__ | Entry node for Module code.b_condition | definition | +| b_condition.py:0 | Global Variable __package__ | Entry node for Module code.b_condition | definition | +| b_condition.py:0 | Global Variable double_attr_check | Entry node for Module code.b_condition | definition | +| b_condition.py:0 | Global Variable f | Entry node for Module code.b_condition | definition | +| b_condition.py:0 | Global Variable g | Entry node for Module code.b_condition | definition | +| b_condition.py:0 | Global Variable h | Entry node for Module code.b_condition | definition | +| b_condition.py:0 | Global Variable k | Entry node for Module code.b_condition | definition | +| b_condition.py:0 | Global Variable loop | Entry node for Module code.b_condition | definition | +| b_condition.py:0 | Global Variable not_or_not | Entry node for Module code.b_condition | definition | +| b_condition.py:0 | Global Variable odasa6261 | Entry node for Module code.b_condition | definition | +| b_condition.py:0 | Global Variable split_bool1 | Entry node for Module code.b_condition | definition | +| b_condition.py:0 | Global Variable v2 | Entry node for Module code.b_condition | definition | +| b_condition.py:4 | Global Variable f | ControlFlowNode for f | definition | +| b_condition.py:4 | Local Variable x | Entry node for Function f | definition | +| b_condition.py:4 | Local Variable y | ControlFlowNode for y | definition | +| b_condition.py:5 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:8 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:9 | Local Variable x | ControlFlowNode for use() | refinement | +| b_condition.py:11 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:14 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:15 | Local Variable x | ControlFlowNode for use() | refinement | +| b_condition.py:17 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:20 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:21 | Local Variable x | ControlFlowNode for use() | refinement | +| b_condition.py:23 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:25 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:26 | Local Variable x | ControlFlowNode for use() | refinement | +| b_condition.py:28 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:29 | Local Variable x | ControlFlowNode for use() | refinement | +| b_condition.py:31 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:33 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:34 | Local Variable x | ControlFlowNode for use() | refinement | +| b_condition.py:36 | Local Variable x | ControlFlowNode for isinstance() | refinement | +| b_condition.py:37 | Local Variable x | ControlFlowNode for use() | refinement | +| b_condition.py:39 | Global Variable v2 | ControlFlowNode for v2 | definition | +| b_condition.py:41 | Global Variable v2 | ControlFlowNode for Attribute | refinement | +| b_condition.py:50 | Global Variable g | ControlFlowNode for g | definition | +| b_condition.py:50 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:55 | Global Variable loop | ControlFlowNode for loop | definition | +| b_condition.py:55 | Local Variable seq | ControlFlowNode for seq | definition | +| b_condition.py:55 | Local Variable v | Entry node for Function loop | definition | +| b_condition.py:56 | Local Variable v | ControlFlowNode for v | definition | +| b_condition.py:58 | Local Variable v | ControlFlowNode for use() | refinement | +| b_condition.py:61 | Global Variable double_attr_check | ControlFlowNode for double_attr_check | definition | +| b_condition.py:61 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:61 | Local Variable y | ControlFlowNode for y | definition | +| b_condition.py:69 | Global Variable h | ControlFlowNode for h | definition | +| b_condition.py:69 | Local Variable b | Entry node for Function h | definition | +| b_condition.py:70 | Local Variable b | ControlFlowNode for b | definition | +| b_condition.py:72 | Local Variable b | ControlFlowNode for b | definition | +| b_condition.py:75 | Global Variable k | ControlFlowNode for k | definition | +| b_condition.py:75 | Local Variable t | Entry node for Function k | definition | +| b_condition.py:76 | Local Variable t | ControlFlowNode for t | definition | +| b_condition.py:78 | Local Variable t | ControlFlowNode for t | definition | +| b_condition.py:79 | Local Variable t | ControlFlowNode for use() | refinement | +| b_condition.py:81 | Global Variable odasa6261 | ControlFlowNode for odasa6261 | definition | +| b_condition.py:81 | Local Variable bar | Entry node for Function odasa6261 | definition | +| b_condition.py:81 | Local Variable foo | ControlFlowNode for foo | definition | +| b_condition.py:82 | Local Variable foo | ControlFlowNode for callable() | refinement | +| b_condition.py:83 | Local Variable bar | ControlFlowNode for bar | definition | +| b_condition.py:83 | Local Variable foo | Entry node for Function bar | definition | +| b_condition.py:84 | Local Variable foo | ControlFlowNode for foo() | refinement | +| b_condition.py:87 | Global Variable split_bool1 | ControlFlowNode for split_bool1 | definition | +| b_condition.py:87 | Local Variable x | ControlFlowNode for x | definition | +| b_condition.py:87 | Local Variable y | ControlFlowNode for y | definition | +| b_condition.py:90 | Local Variable x | ControlFlowNode for UnaryExpr | refinement | +| b_condition.py:90 | Local Variable x | ControlFlowNode for x | refinement | +| b_condition.py:92 | Local Variable x | ControlFlowNode for x | refinement | +| b_condition.py:93 | Local Variable y | ControlFlowNode for use() | refinement | +| b_condition.py:95 | Local Variable y | ControlFlowNode for use() | refinement | +| b_condition.py:96 | Local Variable y | ControlFlowNode for y | refinement | +| b_condition.py:97 | Local Variable x | ControlFlowNode for use() | refinement | +| b_condition.py:99 | Local Variable x | ControlFlowNode for use() | refinement | +| b_condition.py:101 | Global Variable not_or_not | ControlFlowNode for not_or_not | definition | +| b_condition.py:101 | Local Variable a | ControlFlowNode for a | definition | +| b_condition.py:101 | Local Variable a | Entry node for Function not_or_not | definition | +| b_condition.py:102 | Local Variable a | ControlFlowNode for isinstance() | refinement | diff --git a/python/ql/test/library-tests/PointsTo/new/SourceNodeDefinitions.ql b/python/ql/test/library-tests/PointsTo/new/SourceNodeDefinitions.ql new file mode 100644 index 000000000000..95341360bf4d --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/SourceNodeDefinitions.ql @@ -0,0 +1,13 @@ + +import python +import semmle.dataflow.SSA +import semmle.python.pointsto.PointsTo + +import Util + +from SsaSourceVariable var, ControlFlowNode defn, string kind +where +var.hasDefiningNode(defn) and kind = "definition" +or +var.hasRefinement(_, defn) and kind = "refinement" +select locate(defn.getLocation(), "ab"), var.(Variable), defn.toString(), kind diff --git a/python/ql/test/library-tests/PointsTo/new/SsaAttr.expected b/python/ql/test/library-tests/PointsTo/new/SsaAttr.expected new file mode 100644 index 000000000000..f518b462be18 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/SsaAttr.expected @@ -0,0 +1,91 @@ +| b_condition.py:41 | v2_2 | x | AttributeAssignment 'x'(v2_1) | int 1 | import | +| b_condition.py:43 | v2_3 | x | Pi(v2_2) [true] | int 1 | import | +| b_condition.py:47 | v2_4 | x | Pi(v2_2) [false] | int 1 | import | +| b_condition.py:47 | v2_5 | x | phi(v2_3, v2_4) | int 1 | import | +| f_finally.py:3 | self_3 | _closed | phi(self_1, self_2) | bool True | runtime | +| f_finally.py:4 | self_1 | _closed | AttributeAssignment '_closed'(self_0) | bool True | runtime | +| f_finally.py:10 | self_2 | _closed | AttributeAssignment '_close'(self_1) | bool True | runtime | +| g_class_init.py:6 | self_1 | y | SelfCallsiteRefinement(self_0) | int 2 | runtime | +| g_class_init.py:6 | self_1 | z | SelfCallsiteRefinement(self_0) | int 3 | runtime | +| g_class_init.py:7 | self_2 | x | AttributeAssignment 'x'(self_1) | int 1 | runtime | +| g_class_init.py:7 | self_2 | y | AttributeAssignment 'x'(self_1) | int 2 | runtime | +| g_class_init.py:7 | self_2 | z | AttributeAssignment 'x'(self_1) | int 3 | runtime | +| g_class_init.py:10 | self_1 | y | AttributeAssignment 'y'(self_0) | int 2 | code/g_class_init.py:6 from runtime | +| g_class_init.py:11 | self_2 | y | SelfCallsiteRefinement(self_1) | int 2 | code/g_class_init.py:6 from runtime | +| g_class_init.py:11 | self_2 | z | SelfCallsiteRefinement(self_1) | int 3 | code/g_class_init.py:6 from runtime | +| g_class_init.py:13 | self_0 | y | ParameterDefinition | int 2 | code/g_class_init.py:11 from code/g_class_init.py:6 from runtime | +| g_class_init.py:14 | self_1 | y | AttributeAssignment 'z'(self_0) | int 2 | code/g_class_init.py:11 from code/g_class_init.py:6 from runtime | +| g_class_init.py:14 | self_1 | z | AttributeAssignment 'z'(self_0) | int 3 | code/g_class_init.py:11 from code/g_class_init.py:6 from runtime | +| g_class_init.py:16 | self_0 | x | ParameterDefinition | int 1 | runtime | +| g_class_init.py:16 | self_0 | y | ParameterDefinition | int 2 | runtime | +| g_class_init.py:16 | self_0 | z | ParameterDefinition | int 3 | runtime | +| g_class_init.py:19 | self_1 | x | Pi(self_0) [true] | int 1 | runtime | +| g_class_init.py:19 | self_1 | y | Pi(self_0) [true] | int 2 | runtime | +| g_class_init.py:19 | self_1 | z | Pi(self_0) [true] | int 3 | runtime | +| g_class_init.py:20 | self_2 | x | Pi(self_0) [false] | int 1 | runtime | +| g_class_init.py:20 | self_2 | z | Pi(self_0) [false] | int 3 | runtime | +| g_class_init.py:20 | self_3 | x | phi(self_1, self_2) | int 1 | runtime | +| g_class_init.py:20 | self_3 | y | phi(self_1, self_2) | int 2 | runtime | +| g_class_init.py:20 | self_3 | z | phi(self_1, self_2) | int 3 | runtime | +| g_class_init.py:46 | self_3 | version | phi(self_1, self_2) | 'v2' | runtime | +| g_class_init.py:46 | self_3 | version | phi(self_1, self_2) | 'v3' | runtime | +| g_class_init.py:48 | self_1 | version | AttributeAssignment 'version'(self_0) | 'v2' | runtime | +| g_class_init.py:50 | self_2 | version | AttributeAssignment 'version'(self_0) | 'v3' | runtime | +| g_class_init.py:52 | self_0 | version | ParameterDefinition | 'v2' | runtime | +| g_class_init.py:52 | self_0 | version | ParameterDefinition | 'v3' | runtime | +| g_class_init.py:52 | self_2 | version | Pi(self_0) [false] | 'v3' | runtime | +| g_class_init.py:52 | self_3 | version | phi(self_1, self_2) | 'v2' | runtime | +| g_class_init.py:52 | self_3 | version | phi(self_1, self_2) | 'v3' | runtime | +| g_class_init.py:54 | self_1 | version | Pi(self_0) [true] | 'v2' | runtime | +| i_imports.py:7 | *_1 | x | ImportStarRefinement(*_0) | float 1.0 | import | +| i_imports.py:7 | *_1 | y | ImportStarRefinement(*_0) | float 2.0 | import | +| i_imports.py:27 | *_2 | module1 | ImportStarRefinement(*_1) | Module code.test_package.module1 | import | +| i_imports.py:27 | *_2 | module2 | ImportStarRefinement(*_1) | Module code.test_package.module2 | import | +| i_imports.py:27 | *_2 | p | ImportStarRefinement(*_1) | int 1 | import | +| i_imports.py:27 | *_2 | q | ImportStarRefinement(*_1) | int 2 | import | +| i_imports.py:27 | *_2 | r | ImportStarRefinement(*_1) | Dict | import | +| i_imports.py:27 | *_2 | s | ImportStarRefinement(*_1) | NoneType None | import | +| i_imports.py:27 | *_2 | x | ImportStarRefinement(*_1) | float 1.0 | import | +| i_imports.py:27 | *_2 | y | ImportStarRefinement(*_1) | float 2.0 | import | +| k_getsetattr.py:6 | self_0 | a | ParameterDefinition | float 7.0 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:6 | self_0 | c | ParameterDefinition | int 2 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:7 | self_1 | a | ArgumentRefinement(self_0) | int 0 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:7 | self_1 | a | ArgumentRefinement(self_0) | int 0 | runtime | +| k_getsetattr.py:7 | self_1 | c | ArgumentRefinement(self_0) | int 2 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:8 | self_2 | a | ArgumentRefinement(self_1) | int 0 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:8 | self_2 | a | ArgumentRefinement(self_1) | int 0 | runtime | +| k_getsetattr.py:8 | self_2 | b | ArgumentRefinement(self_1) | int 1 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:8 | self_2 | b | ArgumentRefinement(self_1) | int 1 | runtime | +| k_getsetattr.py:8 | self_2 | c | ArgumentRefinement(self_1) | int 2 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:9 | self_3 | a | ArgumentRefinement(self_2) | int 0 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:9 | self_3 | a | ArgumentRefinement(self_2) | int 0 | runtime | +| k_getsetattr.py:9 | self_3 | b | ArgumentRefinement(self_2) | int 1 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:9 | self_3 | b | ArgumentRefinement(self_2) | int 1 | runtime | +| k_getsetattr.py:9 | self_3 | c | ArgumentRefinement(self_2) | int 2 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:10 | self_4 | a | ArgumentRefinement(self_3) | int 0 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:10 | self_4 | a | ArgumentRefinement(self_3) | int 0 | runtime | +| k_getsetattr.py:10 | self_4 | b | ArgumentRefinement(self_3) | int 1 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:10 | self_4 | b | ArgumentRefinement(self_3) | int 1 | runtime | +| k_getsetattr.py:10 | self_4 | c | ArgumentRefinement(self_3) | int 2 | code/k_getsetattr.py:15 from runtime | +| k_getsetattr.py:13 | self_1 | a | ArgumentRefinement(self_0) | float 7.0 | runtime | +| k_getsetattr.py:14 | self_2 | a | ArgumentRefinement(self_1) | float 7.0 | runtime | +| k_getsetattr.py:14 | self_2 | c | ArgumentRefinement(self_1) | int 2 | runtime | +| k_getsetattr.py:15 | self_3 | a | SelfCallsiteRefinement(self_2) | int 0 | runtime | +| k_getsetattr.py:15 | self_3 | b | SelfCallsiteRefinement(self_2) | int 1 | runtime | +| k_getsetattr.py:15 | self_3 | c | SelfCallsiteRefinement(self_2) | int 2 | runtime | +| k_getsetattr.py:16 | self_4 | a | ArgumentRefinement(self_3) | int 0 | runtime | +| k_getsetattr.py:16 | self_4 | b | ArgumentRefinement(self_3) | int 1 | runtime | +| k_getsetattr.py:16 | self_4 | c | ArgumentRefinement(self_3) | int 2 | runtime | +| k_getsetattr.py:17 | self_5 | a | ArgumentRefinement(self_4) | int 0 | runtime | +| k_getsetattr.py:17 | self_5 | b | ArgumentRefinement(self_4) | int 1 | runtime | +| k_getsetattr.py:17 | self_5 | c | ArgumentRefinement(self_4) | int 2 | runtime | +| k_getsetattr.py:18 | self_6 | a | ArgumentRefinement(self_5) | int 0 | runtime | +| k_getsetattr.py:18 | self_6 | b | ArgumentRefinement(self_5) | int 1 | runtime | +| k_getsetattr.py:18 | self_6 | c | ArgumentRefinement(self_5) | int 2 | runtime | +| k_getsetattr.py:25 | c1_1 | a | AttributeAssignment 'a'(c1_0) | int 10 | runtime | +| k_getsetattr.py:27 | c2_1 | a | AttributeAssignment 'a'(c2_0) | int 20 | runtime | +| k_getsetattr.py:28 | c2_2 | a | phi(c2_0, c2_1) | int 20 | runtime | +| k_getsetattr.py:31 | c3_1 | a | AttributeAssignment 'a'(c3_0) | int 30 | runtime | +| m_attributes.py:6 | self_1 | a | AttributeAssignment 'a'(self_0) | int 17 | runtime | +| m_attributes.py:6 | self_1 | a | AttributeAssignment 'a'(self_0) | int 100 | code/m_attributes.py:13 from import | +| m_attributes.py:8 | self_0 | a | ParameterDefinition | int 17 | runtime | diff --git a/python/ql/test/library-tests/PointsTo/new/SsaAttr.ql b/python/ql/test/library-tests/PointsTo/new/SsaAttr.ql new file mode 100644 index 000000000000..4a2fac535cfd --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/SsaAttr.ql @@ -0,0 +1,12 @@ + +import python +private import semmle.python.pointsto.PointsTo +private import semmle.python.pointsto.PointsToContext +import Util + +from EssaVariable var, string name, Object o, PointsToContext ctx +where PointsTo::Test::ssa_variable_named_attribute_points_to(var, ctx, name, o, _, _) +select +locate(var.getDefinition().getLocation(), "abdfgikm"), var.getRepresentation(), +name, var.getDefinition().getRepresentation(), repr(o), ctx + diff --git a/python/ql/test/library-tests/PointsTo/new/SsaUses.expected b/python/ql/test/library-tests/PointsTo/new/SsaUses.expected new file mode 100644 index 000000000000..171e9174cbae --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/SsaUses.expected @@ -0,0 +1,653 @@ +| __init__.py:0 | *_2 | Exit node for Module code.test_package.__init__ | +| __init__.py:0 | __name___0 | Exit node for Module code.__init__ | +| __init__.py:0 | __name___0 | Exit node for Module code.package.__init__ | +| __init__.py:0 | __name___2 | Exit node for Module code.test_package.__init__ | +| __init__.py:0 | __package___0 | Exit node for Module code.__init__ | +| __init__.py:0 | __package___0 | Exit node for Module code.package.__init__ | +| __init__.py:0 | __package___2 | Exit node for Module code.test_package.__init__ | +| __init__.py:0 | module2_1 | Exit node for Module code.package.__init__ | +| __init__.py:0 | module3_0 | Exit node for Module code.package.__init__ | +| __init__.py:0 | module4_0 | Exit node for Module code.package.__init__ | +| __init__.py:0 | module5_0 | Exit node for Module code.package.__init__ | +| __init__.py:0 | moduleX_1 | Exit node for Module code.package.__init__ | +| __init__.py:0 | module_0 | Exit node for Module code.package.__init__ | +| __init__.py:0 | sys_2 | Exit node for Module code.test_package.__init__ | +| __init__.py:1 | *_0 | ControlFlowNode for from module1 import * | +| __init__.py:1 | __name___0 | ControlFlowNode for from module1 import * | +| __init__.py:1 | __package___0 | ControlFlowNode for from module1 import * | +| __init__.py:1 | sys_0 | ControlFlowNode for from module1 import * | +| __init__.py:2 | *_1 | ControlFlowNode for from module2 import * | +| __init__.py:2 | __name___1 | ControlFlowNode for from module2 import * | +| __init__.py:2 | __package___1 | ControlFlowNode for from module2 import * | +| __init__.py:2 | sys_1 | ControlFlowNode for from module2 import * | +| __init__.py:4 | module2_0 | ControlFlowNode for ImportMember | +| __init__.py:6 | module2_1 | ControlFlowNode for ImportMember | +| __init__.py:7 | module3_0 | ControlFlowNode for ImportMember | +| __init__.py:8 | moduleX_0 | ControlFlowNode for ImportMember | +| a_simple.py:0 | C_0 | Exit node for Module code.a_simple | +| a_simple.py:0 | __name___0 | Exit node for Module code.a_simple | +| a_simple.py:0 | __package___0 | Exit node for Module code.a_simple | +| a_simple.py:0 | f1_0 | Exit node for Module code.a_simple | +| a_simple.py:0 | f_0 | Exit node for Module code.a_simple | +| a_simple.py:0 | func_0 | Exit node for Module code.a_simple | +| a_simple.py:0 | i1_0 | Exit node for Module code.a_simple | +| a_simple.py:0 | multi_loop_0 | Exit node for Module code.a_simple | +| a_simple.py:0 | multi_loop_in_try_0 | Exit node for Module code.a_simple | +| a_simple.py:0 | s_0 | Exit node for Module code.a_simple | +| a_simple.py:0 | vararg_kwarg_0 | Exit node for Module code.a_simple | +| a_simple.py:0 | with_definition_0 | Exit node for Module code.a_simple | +| a_simple.py:14 | d_0 | Exit node for Function vararg_kwarg | +| a_simple.py:14 | t_0 | Exit node for Function vararg_kwarg | +| a_simple.py:15 | t_0 | ControlFlowNode for t | +| a_simple.py:16 | d_0 | ControlFlowNode for d | +| a_simple.py:18 | seq_0 | Exit node for Function multi_loop | +| a_simple.py:18 | x_1 | Exit node for Function multi_loop | +| a_simple.py:18 | y_1 | Exit node for Function multi_loop | +| a_simple.py:20 | seq_0 | ControlFlowNode for seq | +| a_simple.py:21 | x_2 | ControlFlowNode for x | +| a_simple.py:23 | x_0 | Exit node for Function with_definition | +| a_simple.py:23 | y_0 | Exit node for Function with_definition | +| a_simple.py:24 | x_0 | ControlFlowNode for x | +| a_simple.py:25 | y_0 | ControlFlowNode for y | +| a_simple.py:27 | p_1 | Exit node for Function multi_loop_in_try | +| a_simple.py:27 | q_1 | Exit node for Function multi_loop_in_try | +| a_simple.py:27 | x_0 | Exit node for Function multi_loop_in_try | +| a_simple.py:29 | x_0 | ControlFlowNode for x | +| a_simple.py:30 | p_2 | ControlFlowNode for p | +| a_simple.py:34 | args_0 | Exit node for Function f | +| a_simple.py:34 | kwargs_0 | Exit node for Function f | +| a_simple.py:35 | args_0 | ControlFlowNode for args | +| a_simple.py:36 | kwargs_0 | ControlFlowNode for kwargs | +| b_condition.py:0 | __name___0 | Exit node for Module code.b_condition | +| b_condition.py:0 | __package___0 | Exit node for Module code.b_condition | +| b_condition.py:0 | double_attr_check_1 | Exit node for Module code.b_condition | +| b_condition.py:0 | f_0 | Exit node for Module code.b_condition | +| b_condition.py:0 | g_1 | Exit node for Module code.b_condition | +| b_condition.py:0 | h_1 | Exit node for Module code.b_condition | +| b_condition.py:0 | k_1 | Exit node for Module code.b_condition | +| b_condition.py:0 | loop_1 | Exit node for Module code.b_condition | +| b_condition.py:0 | not_or_not_1 | Exit node for Module code.b_condition | +| b_condition.py:0 | odasa6261_1 | Exit node for Module code.b_condition | +| b_condition.py:0 | split_bool1_1 | Exit node for Module code.b_condition | +| b_condition.py:0 | v2_5 | Exit node for Module code.b_condition | +| b_condition.py:4 | x_25 | Exit node for Function f | +| b_condition.py:4 | y_0 | Exit node for Function f | +| b_condition.py:7 | x_0 | ControlFlowNode for x | +| b_condition.py:9 | x_3 | ControlFlowNode for x | +| b_condition.py:13 | x_4 | ControlFlowNode for x | +| b_condition.py:15 | x_7 | ControlFlowNode for x | +| b_condition.py:19 | x_8 | ControlFlowNode for x | +| b_condition.py:21 | x_11 | ControlFlowNode for x | +| b_condition.py:25 | x_12 | ControlFlowNode for x | +| b_condition.py:25 | x_13 | ControlFlowNode for x | +| b_condition.py:26 | x_14 | ControlFlowNode for x | +| b_condition.py:29 | x_17 | ControlFlowNode for x | +| b_condition.py:32 | x_18 | ControlFlowNode for x | +| b_condition.py:34 | x_21 | ControlFlowNode for x | +| b_condition.py:36 | x_22 | ControlFlowNode for x | +| b_condition.py:37 | x_24 | ControlFlowNode for x | +| b_condition.py:39 | __name___0 | ControlFlowNode for thing() | +| b_condition.py:39 | __package___0 | ControlFlowNode for thing() | +| b_condition.py:39 | double_attr_check_0 | ControlFlowNode for thing() | +| b_condition.py:39 | f_0 | ControlFlowNode for thing() | +| b_condition.py:39 | g_0 | ControlFlowNode for thing() | +| b_condition.py:39 | h_0 | ControlFlowNode for thing() | +| b_condition.py:39 | k_0 | ControlFlowNode for thing() | +| b_condition.py:39 | loop_0 | ControlFlowNode for thing() | +| b_condition.py:39 | not_or_not_0 | ControlFlowNode for thing() | +| b_condition.py:39 | odasa6261_0 | ControlFlowNode for thing() | +| b_condition.py:39 | split_bool1_0 | ControlFlowNode for thing() | +| b_condition.py:39 | v2_0 | ControlFlowNode for thing() | +| b_condition.py:41 | v2_1 | ControlFlowNode for v2 | +| b_condition.py:42 | v2_2 | ControlFlowNode for v2 | +| b_condition.py:43 | __name___0 | ControlFlowNode for use() | +| b_condition.py:43 | __package___0 | ControlFlowNode for use() | +| b_condition.py:43 | double_attr_check_0 | ControlFlowNode for use() | +| b_condition.py:43 | f_0 | ControlFlowNode for use() | +| b_condition.py:43 | g_0 | ControlFlowNode for use() | +| b_condition.py:43 | h_0 | ControlFlowNode for use() | +| b_condition.py:43 | k_0 | ControlFlowNode for use() | +| b_condition.py:43 | loop_0 | ControlFlowNode for use() | +| b_condition.py:43 | not_or_not_0 | ControlFlowNode for use() | +| b_condition.py:43 | odasa6261_0 | ControlFlowNode for use() | +| b_condition.py:43 | split_bool1_0 | ControlFlowNode for use() | +| b_condition.py:43 | v2_3 | ControlFlowNode for use() | +| b_condition.py:43 | v2_3 | ControlFlowNode for v2 | +| b_condition.py:44 | __name___0 | ControlFlowNode for use() | +| b_condition.py:44 | __package___0 | ControlFlowNode for use() | +| b_condition.py:44 | double_attr_check_0 | ControlFlowNode for use() | +| b_condition.py:44 | f_0 | ControlFlowNode for use() | +| b_condition.py:44 | g_0 | ControlFlowNode for use() | +| b_condition.py:44 | h_0 | ControlFlowNode for use() | +| b_condition.py:44 | k_0 | ControlFlowNode for use() | +| b_condition.py:44 | loop_0 | ControlFlowNode for use() | +| b_condition.py:44 | not_or_not_0 | ControlFlowNode for use() | +| b_condition.py:44 | odasa6261_0 | ControlFlowNode for use() | +| b_condition.py:44 | split_bool1_0 | ControlFlowNode for use() | +| b_condition.py:44 | v2_3 | ControlFlowNode for use() | +| b_condition.py:44 | v2_3 | ControlFlowNode for v2 | +| b_condition.py:50 | x_3 | Exit node for Function g | +| b_condition.py:51 | x_0 | ControlFlowNode for x | +| b_condition.py:52 | x_1 | ControlFlowNode for x | +| b_condition.py:55 | seq_0 | Exit node for Function loop | +| b_condition.py:55 | v_2 | Exit node for Function loop | +| b_condition.py:56 | seq_0 | ControlFlowNode for seq | +| b_condition.py:57 | v_3 | ControlFlowNode for v | +| b_condition.py:58 | v_4 | ControlFlowNode for v | +| b_condition.py:61 | x_7 | Exit node for Function double_attr_check | +| b_condition.py:61 | y_3 | Exit node for Function double_attr_check | +| b_condition.py:62 | x_0 | ControlFlowNode for x | +| b_condition.py:64 | y_0 | ControlFlowNode for y | +| b_condition.py:65 | x_2 | ControlFlowNode for x | +| b_condition.py:66 | x_3 | ControlFlowNode for x | +| b_condition.py:69 | b_3 | Exit node for Function h | +| b_condition.py:71 | b_0 | ControlFlowNode for b | +| b_condition.py:73 | b_3 | ControlFlowNode for b | +| b_condition.py:75 | t_4 | Exit node for Function k | +| b_condition.py:77 | t_0 | ControlFlowNode for t | +| b_condition.py:79 | t_3 | ControlFlowNode for t | +| b_condition.py:81 | bar_2 | Exit node for Function odasa6261 | +| b_condition.py:81 | foo_5 | Exit node for Function odasa6261 | +| b_condition.py:82 | foo_0 | ControlFlowNode for callable() | +| b_condition.py:82 | foo_0 | ControlFlowNode for foo | +| b_condition.py:84 | foo_3 | ControlFlowNode for foo | +| b_condition.py:84 | foo_3 | ControlFlowNode for foo() | +| b_condition.py:87 | x_3 | Exit node for Function split_bool1 | +| b_condition.py:87 | x_8 | Exit node for Function split_bool1 | +| b_condition.py:87 | y_3 | Exit node for Function split_bool1 | +| b_condition.py:87 | y_6 | Exit node for Function split_bool1 | +| b_condition.py:88 | x_0 | ControlFlowNode for x | +| b_condition.py:88 | y_0 | ControlFlowNode for y | +| b_condition.py:90 | x_1 | ControlFlowNode for x | +| b_condition.py:90 | x_4 | ControlFlowNode for x | +| b_condition.py:90 | y_0 | ControlFlowNode for y | +| b_condition.py:92 | x_5 | ControlFlowNode for x | +| b_condition.py:92 | x_6 | ControlFlowNode for x | +| b_condition.py:93 | y_4 | ControlFlowNode for y | +| b_condition.py:95 | y_1 | ControlFlowNode for y | +| b_condition.py:96 | y_2 | ControlFlowNode for y | +| b_condition.py:96 | y_5 | ControlFlowNode for y | +| b_condition.py:97 | x_2 | ControlFlowNode for x | +| b_condition.py:99 | x_7 | ControlFlowNode for x | +| b_condition.py:101 | a_4 | Exit node for Function not_or_not | +| b_condition.py:102 | a_0 | ControlFlowNode for a | +| b_condition.py:104 | a_2 | ControlFlowNode for a | +| b_condition.py:105 | a_3 | ControlFlowNode for a | +| d_globals.py:0 | D_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | Ugly_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | X_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | __name___0 | Exit node for Module code.d_globals | +| d_globals.py:0 | __package___0 | Exit node for Module code.d_globals | +| d_globals.py:0 | assign_global_0 | Exit node for Module code.d_globals | +| d_globals.py:0 | dict_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | g1_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | g2_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | g3_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | g4_6 | Exit node for Module code.d_globals | +| d_globals.py:0 | get_g4_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | glob_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | init_0 | Exit node for Module code.d_globals | +| d_globals.py:0 | j_0 | Exit node for Module code.d_globals | +| d_globals.py:0 | k_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | modinit_2 | Exit node for Module code.d_globals | +| d_globals.py:0 | outer_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | redefine_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | set_g4_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | set_g4_indirect_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | tuple_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | use_list_attribute_1 | Exit node for Module code.d_globals | +| d_globals.py:0 | x_3 | Exit node for Module code.d_globals | +| d_globals.py:0 | y_3 | Exit node for Module code.d_globals | +| d_globals.py:0 | z_1 | Exit node for Module code.d_globals | +| d_globals.py:2 | g1_2 | Exit node for Function j | +| d_globals.py:2 | g2_2 | Exit node for Function j | +| d_globals.py:2 | g3_2 | Exit node for Function j | +| d_globals.py:2 | g4_1 | Exit node for Function j | +| d_globals.py:2 | glob_2 | Exit node for Function j | +| d_globals.py:2 | z_2 | Exit node for Function j | +| d_globals.py:3 | dict_2 | ControlFlowNode for dict | +| d_globals.py:3 | tuple_2 | ControlFlowNode for tuple | +| d_globals.py:4 | dict_0 | ControlFlowNode for dict | +| d_globals.py:6 | dict_1 | ControlFlowNode for dict | +| d_globals.py:7 | tuple_0 | ControlFlowNode for tuple | +| d_globals.py:8 | tuple_1 | ControlFlowNode for tuple | +| d_globals.py:16 | g1_3 | Exit node for Function assign_global | +| d_globals.py:16 | g2_3 | Exit node for Function assign_global | +| d_globals.py:16 | g3_3 | Exit node for Function assign_global | +| d_globals.py:16 | g4_2 | Exit node for Function assign_global | +| d_globals.py:16 | glob_3 | Exit node for Function assign_global | +| d_globals.py:16 | z_3 | Exit node for Function assign_global | +| d_globals.py:19 | g1_3 | ControlFlowNode for g1 | +| d_globals.py:25 | g1_4 | Exit node for Function init | +| d_globals.py:25 | g2_4 | Exit node for Function init | +| d_globals.py:25 | g3_4 | Exit node for Function init | +| d_globals.py:25 | g4_3 | Exit node for Function init | +| d_globals.py:25 | glob_4 | Exit node for Function init | +| d_globals.py:25 | z_4 | Exit node for Function init | +| d_globals.py:29 | D_0 | ControlFlowNode for init() | +| d_globals.py:29 | Ugly_0 | ControlFlowNode for init() | +| d_globals.py:29 | X_0 | ControlFlowNode for init() | +| d_globals.py:29 | __name___0 | ControlFlowNode for init() | +| d_globals.py:29 | __package___0 | ControlFlowNode for init() | +| d_globals.py:29 | assign_global_0 | ControlFlowNode for init() | +| d_globals.py:29 | dict_1 | ControlFlowNode for init() | +| d_globals.py:29 | g1_0 | ControlFlowNode for init() | +| d_globals.py:29 | g2_0 | ControlFlowNode for init() | +| d_globals.py:29 | g3_0 | ControlFlowNode for init() | +| d_globals.py:29 | g4_0 | ControlFlowNode for init() | +| d_globals.py:29 | get_g4_0 | ControlFlowNode for init() | +| d_globals.py:29 | glob_0 | ControlFlowNode for init() | +| d_globals.py:29 | init_0 | ControlFlowNode for init | +| d_globals.py:29 | init_0 | ControlFlowNode for init() | +| d_globals.py:29 | j_0 | ControlFlowNode for init() | +| d_globals.py:29 | k_0 | ControlFlowNode for init() | +| d_globals.py:29 | modinit_0 | ControlFlowNode for init() | +| d_globals.py:29 | outer_0 | ControlFlowNode for init() | +| d_globals.py:29 | redefine_0 | ControlFlowNode for init() | +| d_globals.py:29 | set_g4_0 | ControlFlowNode for init() | +| d_globals.py:29 | set_g4_indirect_0 | ControlFlowNode for init() | +| d_globals.py:29 | tuple_1 | ControlFlowNode for init() | +| d_globals.py:29 | use_list_attribute_0 | ControlFlowNode for init() | +| d_globals.py:29 | x_0 | ControlFlowNode for init() | +| d_globals.py:29 | y_0 | ControlFlowNode for init() | +| d_globals.py:29 | z_0 | ControlFlowNode for init() | +| d_globals.py:30 | g2_1 | ControlFlowNode for g2 | +| d_globals.py:35 | __init___0 | Exit node for Class Ugly | +| d_globals.py:35 | meth_0 | Exit node for Class Ugly | +| d_globals.py:37 | g1_5 | Exit node for Function __init__ | +| d_globals.py:37 | g2_5 | Exit node for Function __init__ | +| d_globals.py:37 | g3_5 | Exit node for Function __init__ | +| d_globals.py:37 | g4_4 | Exit node for Function __init__ | +| d_globals.py:37 | glob_5 | Exit node for Function __init__ | +| d_globals.py:37 | self_0 | Exit node for Function __init__ | +| d_globals.py:37 | z_5 | Exit node for Function __init__ | +| d_globals.py:41 | g1_6 | Exit node for Function meth | +| d_globals.py:41 | g2_6 | Exit node for Function meth | +| d_globals.py:41 | g3_6 | Exit node for Function meth | +| d_globals.py:41 | g4_5 | Exit node for Function meth | +| d_globals.py:41 | glob_6 | Exit node for Function meth | +| d_globals.py:41 | self_0 | Exit node for Function meth | +| d_globals.py:41 | z_6 | Exit node for Function meth | +| d_globals.py:42 | g3_6 | ControlFlowNode for g3 | +| d_globals.py:47 | x_1 | ControlFlowNode for x | +| d_globals.py:59 | y_3 | ControlFlowNode for y | +| d_globals.py:62 | X_0 | ControlFlowNode for ClassExpr | +| d_globals.py:62 | g3_1 | ControlFlowNode for ClassExpr | +| d_globals.py:62 | v4_0 | Exit node for Class X | +| d_globals.py:62 | y_1 | Exit node for Class X | +| d_globals.py:62 | y_3 | ControlFlowNode for ClassExpr | +| d_globals.py:63 | y_0 | ControlFlowNode for y | +| d_globals.py:63 | y_4 | ControlFlowNode for y | +| d_globals.py:65 | X_2 | ControlFlowNode for X | +| d_globals.py:66 | g3_7 | ControlFlowNode for g3 | +| d_globals.py:70 | arg_0 | Exit node for Function k | +| d_globals.py:70 | g1_7 | Exit node for Function k | +| d_globals.py:70 | g2_7 | Exit node for Function k | +| d_globals.py:70 | g3_8 | Exit node for Function k | +| d_globals.py:70 | g4_7 | Exit node for Function k | +| d_globals.py:70 | glob_7 | Exit node for Function k | +| d_globals.py:70 | z_7 | Exit node for Function k | +| d_globals.py:75 | g1_10 | Exit node for Function get_g4 | +| d_globals.py:75 | g2_10 | Exit node for Function get_g4 | +| d_globals.py:75 | g3_11 | Exit node for Function get_g4 | +| d_globals.py:75 | g4_12 | Exit node for Function get_g4 | +| d_globals.py:75 | glob_10 | Exit node for Function get_g4 | +| d_globals.py:75 | z_10 | Exit node for Function get_g4 | +| d_globals.py:76 | g4_8 | ControlFlowNode for g4 | +| d_globals.py:77 | g1_8 | ControlFlowNode for set_g4() | +| d_globals.py:77 | g2_8 | ControlFlowNode for set_g4() | +| d_globals.py:77 | g3_9 | ControlFlowNode for set_g4() | +| d_globals.py:77 | g4_9 | ControlFlowNode for set_g4() | +| d_globals.py:77 | glob_8 | ControlFlowNode for set_g4() | +| d_globals.py:77 | set_g4_2 | ControlFlowNode for set_g4 | +| d_globals.py:77 | z_8 | ControlFlowNode for set_g4() | +| d_globals.py:78 | g4_12 | ControlFlowNode for g4 | +| d_globals.py:80 | g1_12 | Exit node for Function set_g4 | +| d_globals.py:80 | g2_12 | Exit node for Function set_g4 | +| d_globals.py:80 | g3_13 | Exit node for Function set_g4 | +| d_globals.py:80 | g4_14 | Exit node for Function set_g4 | +| d_globals.py:80 | glob_12 | Exit node for Function set_g4 | +| d_globals.py:80 | z_12 | Exit node for Function set_g4 | +| d_globals.py:81 | g1_11 | ControlFlowNode for set_g4_indirect() | +| d_globals.py:81 | g2_11 | ControlFlowNode for set_g4_indirect() | +| d_globals.py:81 | g3_12 | ControlFlowNode for set_g4_indirect() | +| d_globals.py:81 | g4_13 | ControlFlowNode for set_g4_indirect() | +| d_globals.py:81 | glob_11 | ControlFlowNode for set_g4_indirect() | +| d_globals.py:81 | set_g4_indirect_2 | ControlFlowNode for set_g4_indirect | +| d_globals.py:81 | z_11 | ControlFlowNode for set_g4_indirect() | +| d_globals.py:83 | g1_13 | Exit node for Function set_g4_indirect | +| d_globals.py:83 | g2_13 | Exit node for Function set_g4_indirect | +| d_globals.py:83 | g3_14 | Exit node for Function set_g4_indirect | +| d_globals.py:83 | g4_15 | Exit node for Function set_g4_indirect | +| d_globals.py:83 | glob_13 | Exit node for Function set_g4_indirect | +| d_globals.py:83 | z_13 | Exit node for Function set_g4_indirect | +| d_globals.py:92 | modinit_1 | ControlFlowNode for modinit | +| d_globals.py:95 | g1_15 | Exit node for Function outer | +| d_globals.py:95 | g2_15 | Exit node for Function outer | +| d_globals.py:95 | g3_16 | Exit node for Function outer | +| d_globals.py:95 | g4_17 | Exit node for Function outer | +| d_globals.py:95 | glob_15 | Exit node for Function outer | +| d_globals.py:95 | inner_0 | Exit node for Function outer | +| d_globals.py:95 | otherInner_0 | Exit node for Function outer | +| d_globals.py:95 | z_15 | Exit node for Function outer | +| d_globals.py:96 | g1_16 | Exit node for Function inner | +| d_globals.py:96 | g2_16 | Exit node for Function inner | +| d_globals.py:96 | g3_17 | Exit node for Function inner | +| d_globals.py:96 | g4_18 | Exit node for Function inner | +| d_globals.py:96 | glob_16 | Exit node for Function inner | +| d_globals.py:96 | z_16 | Exit node for Function inner | +| d_globals.py:99 | glob_16 | ControlFlowNode for glob | +| d_globals.py:101 | g1_17 | Exit node for Function otherInner | +| d_globals.py:101 | g2_17 | Exit node for Function otherInner | +| d_globals.py:101 | g3_18 | Exit node for Function otherInner | +| d_globals.py:101 | g4_19 | Exit node for Function otherInner | +| d_globals.py:101 | glob_17 | Exit node for Function otherInner | +| d_globals.py:101 | z_17 | Exit node for Function otherInner | +| d_globals.py:102 | glob_17 | ControlFlowNode for glob | +| d_globals.py:104 | g1_14 | ControlFlowNode for inner() | +| d_globals.py:104 | g2_14 | ControlFlowNode for inner() | +| d_globals.py:104 | g3_15 | ControlFlowNode for inner() | +| d_globals.py:104 | g4_16 | ControlFlowNode for inner() | +| d_globals.py:104 | glob_14 | ControlFlowNode for inner() | +| d_globals.py:104 | inner_0 | ControlFlowNode for inner | +| d_globals.py:104 | z_14 | ControlFlowNode for inner() | +| d_globals.py:107 | g1_18 | Exit node for Function redefine | +| d_globals.py:107 | g2_18 | Exit node for Function redefine | +| d_globals.py:107 | g3_19 | Exit node for Function redefine | +| d_globals.py:107 | g4_20 | Exit node for Function redefine | +| d_globals.py:107 | glob_19 | Exit node for Function redefine | +| d_globals.py:107 | z_19 | Exit node for Function redefine | +| d_globals.py:109 | z_18 | ControlFlowNode for z | +| d_globals.py:111 | z_19 | ControlFlowNode for z | +| d_globals.py:112 | glob_18 | ControlFlowNode for glob | +| d_globals.py:114 | glob_19 | ControlFlowNode for glob | +| d_globals.py:118 | __init___0 | Exit node for Class D | +| d_globals.py:118 | foo_0 | Exit node for Class D | +| d_globals.py:120 | g1_19 | Exit node for Function __init__ | +| d_globals.py:120 | g2_19 | Exit node for Function __init__ | +| d_globals.py:120 | g3_20 | Exit node for Function __init__ | +| d_globals.py:120 | g4_21 | Exit node for Function __init__ | +| d_globals.py:120 | glob_20 | Exit node for Function __init__ | +| d_globals.py:120 | self_0 | Exit node for Function __init__ | +| d_globals.py:120 | z_20 | Exit node for Function __init__ | +| d_globals.py:123 | g1_20 | Exit node for Function foo | +| d_globals.py:123 | g2_20 | Exit node for Function foo | +| d_globals.py:123 | g3_21 | Exit node for Function foo | +| d_globals.py:123 | g4_22 | Exit node for Function foo | +| d_globals.py:123 | glob_21 | Exit node for Function foo | +| d_globals.py:123 | self_0 | Exit node for Function foo | +| d_globals.py:123 | z_21 | Exit node for Function foo | +| d_globals.py:124 | dict_3 | ControlFlowNode for dict | +| d_globals.py:126 | g1_22 | Exit node for Function use_list_attribute | +| d_globals.py:126 | g2_22 | Exit node for Function use_list_attribute | +| d_globals.py:126 | g3_23 | Exit node for Function use_list_attribute | +| d_globals.py:126 | g4_24 | Exit node for Function use_list_attribute | +| d_globals.py:126 | glob_23 | Exit node for Function use_list_attribute | +| d_globals.py:126 | l_1 | Exit node for Function use_list_attribute | +| d_globals.py:126 | z_23 | Exit node for Function use_list_attribute | +| d_globals.py:128 | g1_21 | ControlFlowNode for Attribute() | +| d_globals.py:128 | g2_21 | ControlFlowNode for Attribute() | +| d_globals.py:128 | g3_22 | ControlFlowNode for Attribute() | +| d_globals.py:128 | g4_23 | ControlFlowNode for Attribute() | +| d_globals.py:128 | glob_22 | ControlFlowNode for Attribute() | +| d_globals.py:128 | l_0 | ControlFlowNode for l | +| d_globals.py:128 | z_22 | ControlFlowNode for Attribute() | +| d_globals.py:129 | l_1 | ControlFlowNode for l | +| e_temporal.py:0 | __name___0 | Exit node for Module code.e_temporal | +| e_temporal.py:0 | __package___0 | Exit node for Module code.e_temporal | +| e_temporal.py:0 | f_0 | Exit node for Module code.e_temporal | +| e_temporal.py:0 | g_0 | Exit node for Module code.e_temporal | +| e_temporal.py:0 | sys_0 | Exit node for Module code.e_temporal | +| e_temporal.py:0 | x_1 | Exit node for Module code.e_temporal | +| e_temporal.py:5 | sys_1 | ControlFlowNode for sys | +| e_temporal.py:9 | arg_0 | Exit node for Function g | +| e_temporal.py:10 | arg_0 | ControlFlowNode for arg | +| e_temporal.py:12 | __name___0 | ControlFlowNode for f() | +| e_temporal.py:12 | __name___0 | ControlFlowNode for g() | +| e_temporal.py:12 | __package___0 | ControlFlowNode for f() | +| e_temporal.py:12 | __package___0 | ControlFlowNode for g() | +| e_temporal.py:12 | f_0 | ControlFlowNode for f | +| e_temporal.py:12 | f_0 | ControlFlowNode for f() | +| e_temporal.py:12 | f_0 | ControlFlowNode for g() | +| e_temporal.py:12 | g_0 | ControlFlowNode for f() | +| e_temporal.py:12 | g_0 | ControlFlowNode for g | +| e_temporal.py:12 | g_0 | ControlFlowNode for g() | +| e_temporal.py:12 | sys_0 | ControlFlowNode for f() | +| e_temporal.py:12 | sys_0 | ControlFlowNode for g() | +| e_temporal.py:12 | x_0 | ControlFlowNode for f() | +| e_temporal.py:12 | x_0 | ControlFlowNode for g() | +| g_class_init.py:0 | C_0 | Exit node for Module code.g_class_init | +| g_class_init.py:0 | D_0 | Exit node for Module code.g_class_init | +| g_class_init.py:0 | E_0 | Exit node for Module code.g_class_init | +| g_class_init.py:0 | Oddities_0 | Exit node for Module code.g_class_init | +| g_class_init.py:0 | V2_0 | Exit node for Module code.g_class_init | +| g_class_init.py:0 | V3_0 | Exit node for Module code.g_class_init | +| g_class_init.py:0 | __name___0 | Exit node for Module code.g_class_init | +| g_class_init.py:0 | __package___0 | Exit node for Module code.g_class_init | +| g_class_init.py:3 | __init___0 | Exit node for Class C | +| g_class_init.py:3 | _init2_0 | Exit node for Class C | +| g_class_init.py:3 | _init_0 | Exit node for Class C | +| g_class_init.py:3 | method_0 | Exit node for Class C | +| g_class_init.py:5 | self_2 | Exit node for Function __init__ | +| g_class_init.py:6 | self_0 | ControlFlowNode for self | +| g_class_init.py:7 | self_1 | ControlFlowNode for self | +| g_class_init.py:9 | self_2 | Exit node for Function _init | +| g_class_init.py:10 | self_0 | ControlFlowNode for self | +| g_class_init.py:11 | self_1 | ControlFlowNode for self | +| g_class_init.py:13 | self_1 | Exit node for Function _init2 | +| g_class_init.py:14 | self_0 | ControlFlowNode for self | +| g_class_init.py:16 | self_3 | Exit node for Function method | +| g_class_init.py:17 | self_0 | ControlFlowNode for self | +| g_class_init.py:18 | self_0 | ControlFlowNode for self | +| g_class_init.py:19 | self_1 | ControlFlowNode for self | +| g_class_init.py:20 | self_3 | ControlFlowNode for self | +| g_class_init.py:24 | float_1 | Exit node for Class Oddities | +| g_class_init.py:24 | h_0 | Exit node for Class Oddities | +| g_class_init.py:24 | int_1 | Exit node for Class Oddities | +| g_class_init.py:24 | l_0 | Exit node for Class Oddities | +| g_class_init.py:26 | int_0 | ControlFlowNode for int | +| g_class_init.py:27 | float_0 | ControlFlowNode for float | +| g_class_init.py:32 | __init___0 | Exit node for Class D | +| g_class_init.py:34 | self_0 | Exit node for Function __init__ | +| g_class_init.py:35 | D_1 | ControlFlowNode for D | +| g_class_init.py:35 | self_0 | ControlFlowNode for self | +| g_class_init.py:36 | D_2 | ControlFlowNode for D | +| g_class_init.py:36 | self_0 | ControlFlowNode for self | +| g_class_init.py:45 | __init___0 | Exit node for Class E | +| g_class_init.py:45 | meth_0 | Exit node for Class E | +| g_class_init.py:46 | c_3 | Exit node for Function __init__ | +| g_class_init.py:46 | self_3 | Exit node for Function __init__ | +| g_class_init.py:47 | c_0 | ControlFlowNode for c | +| g_class_init.py:48 | V2_1 | ControlFlowNode for V2 | +| g_class_init.py:48 | self_0 | ControlFlowNode for self | +| g_class_init.py:50 | V3_1 | ControlFlowNode for V3 | +| g_class_init.py:50 | self_0 | ControlFlowNode for self | +| g_class_init.py:52 | self_3 | Exit node for Function meth | +| g_class_init.py:53 | V2_2 | ControlFlowNode for V2 | +| g_class_init.py:53 | self_0 | ControlFlowNode for self | +| h_classes.py:0 | Base_1 | Exit node for Module code.h_classes | +| h_classes.py:0 | C_0 | Exit node for Module code.h_classes | +| h_classes.py:0 | D_1 | Exit node for Module code.h_classes | +| h_classes.py:0 | Derived1_1 | Exit node for Module code.h_classes | +| h_classes.py:0 | Derived2_1 | Exit node for Module code.h_classes | +| h_classes.py:0 | Derived3_1 | Exit node for Module code.h_classes | +| h_classes.py:0 | __name___0 | Exit node for Module code.h_classes | +| h_classes.py:0 | __package___0 | Exit node for Module code.h_classes | +| h_classes.py:0 | f_1 | Exit node for Module code.h_classes | +| h_classes.py:0 | k_1 | Exit node for Module code.h_classes | +| h_classes.py:0 | sys_1 | Exit node for Module code.h_classes | +| h_classes.py:0 | thing_1 | Exit node for Module code.h_classes | +| h_classes.py:3 | __init___0 | Exit node for Class C | +| h_classes.py:3 | x_0 | Exit node for Class C | +| h_classes.py:7 | self_1 | Exit node for Function __init__ | +| h_classes.py:8 | self_0 | ControlFlowNode for self | +| h_classes.py:10 | Base_0 | ControlFlowNode for C() | +| h_classes.py:10 | Base_0 | ControlFlowNode for type() | +| h_classes.py:10 | C_0 | ControlFlowNode for C | +| h_classes.py:10 | C_0 | ControlFlowNode for C() | +| h_classes.py:10 | C_0 | ControlFlowNode for type() | +| h_classes.py:10 | D_0 | ControlFlowNode for C() | +| h_classes.py:10 | D_0 | ControlFlowNode for type() | +| h_classes.py:10 | Derived1_0 | ControlFlowNode for C() | +| h_classes.py:10 | Derived1_0 | ControlFlowNode for type() | +| h_classes.py:10 | Derived2_0 | ControlFlowNode for C() | +| h_classes.py:10 | Derived2_0 | ControlFlowNode for type() | +| h_classes.py:10 | Derived3_0 | ControlFlowNode for C() | +| h_classes.py:10 | Derived3_0 | ControlFlowNode for type() | +| h_classes.py:10 | __name___0 | ControlFlowNode for C() | +| h_classes.py:10 | __name___0 | ControlFlowNode for type() | +| h_classes.py:10 | __package___0 | ControlFlowNode for C() | +| h_classes.py:10 | __package___0 | ControlFlowNode for type() | +| h_classes.py:10 | f_0 | ControlFlowNode for C() | +| h_classes.py:10 | f_0 | ControlFlowNode for type() | +| h_classes.py:10 | k_0 | ControlFlowNode for C() | +| h_classes.py:10 | k_0 | ControlFlowNode for type() | +| h_classes.py:10 | sys_0 | ControlFlowNode for C() | +| h_classes.py:10 | sys_0 | ControlFlowNode for type() | +| h_classes.py:10 | thing_0 | ControlFlowNode for C() | +| h_classes.py:10 | thing_0 | ControlFlowNode for type() | +| h_classes.py:11 | Base_0 | ControlFlowNode for type() | +| h_classes.py:11 | C_0 | ControlFlowNode for type() | +| h_classes.py:11 | D_0 | ControlFlowNode for type() | +| h_classes.py:11 | Derived1_0 | ControlFlowNode for type() | +| h_classes.py:11 | Derived2_0 | ControlFlowNode for type() | +| h_classes.py:11 | Derived3_0 | ControlFlowNode for type() | +| h_classes.py:11 | __name___0 | ControlFlowNode for type() | +| h_classes.py:11 | __package___0 | ControlFlowNode for type() | +| h_classes.py:11 | f_0 | ControlFlowNode for type() | +| h_classes.py:11 | k_0 | ControlFlowNode for type() | +| h_classes.py:11 | sys_0 | ControlFlowNode for sys | +| h_classes.py:11 | sys_0 | ControlFlowNode for type() | +| h_classes.py:11 | thing_0 | ControlFlowNode for type() | +| h_classes.py:12 | Base_0 | ControlFlowNode for type() | +| h_classes.py:12 | C_0 | ControlFlowNode for type() | +| h_classes.py:12 | D_0 | ControlFlowNode for type() | +| h_classes.py:12 | Derived1_0 | ControlFlowNode for type() | +| h_classes.py:12 | Derived2_0 | ControlFlowNode for type() | +| h_classes.py:12 | Derived3_0 | ControlFlowNode for type() | +| h_classes.py:12 | __name___0 | ControlFlowNode for type() | +| h_classes.py:12 | __package___0 | ControlFlowNode for type() | +| h_classes.py:12 | f_0 | ControlFlowNode for type() | +| h_classes.py:12 | k_0 | ControlFlowNode for type() | +| h_classes.py:12 | sys_1 | ControlFlowNode for type() | +| h_classes.py:12 | thing_0 | ControlFlowNode for type() | +| h_classes.py:14 | arg_1 | Exit node for Function k | +| h_classes.py:15 | C_1 | ControlFlowNode for C | +| h_classes.py:16 | sys_2 | ControlFlowNode for sys | +| h_classes.py:17 | arg_0 | ControlFlowNode for arg | +| h_classes.py:23 | __init___0 | Exit node for Class Base | +| h_classes.py:25 | choice_5 | Exit node for Function __init__ | +| h_classes.py:25 | self_4 | Exit node for Function __init__ | +| h_classes.py:26 | choice_0 | ControlFlowNode for choice | +| h_classes.py:27 | Derived1_2 | ControlFlowNode for Derived1 | +| h_classes.py:27 | self_0 | ControlFlowNode for self | +| h_classes.py:28 | choice_2 | ControlFlowNode for choice | +| h_classes.py:29 | Derived2_2 | ControlFlowNode for Derived2 | +| h_classes.py:29 | self_0 | ControlFlowNode for self | +| h_classes.py:31 | Derived3_2 | ControlFlowNode for Derived3 | +| h_classes.py:31 | self_0 | ControlFlowNode for self | +| h_classes.py:33 | Base_1 | ControlFlowNode for Base | +| h_classes.py:36 | Base_1 | ControlFlowNode for Base | +| h_classes.py:39 | Base_1 | ControlFlowNode for Base | +| h_classes.py:42 | Base_1 | ControlFlowNode for Base | +| h_classes.py:42 | Base_1 | ControlFlowNode for Base() | +| h_classes.py:42 | Base_1 | ControlFlowNode for unknown() | +| h_classes.py:42 | C_0 | ControlFlowNode for Base() | +| h_classes.py:42 | C_0 | ControlFlowNode for unknown() | +| h_classes.py:42 | D_0 | ControlFlowNode for Base() | +| h_classes.py:42 | D_0 | ControlFlowNode for unknown() | +| h_classes.py:42 | Derived1_1 | ControlFlowNode for Base() | +| h_classes.py:42 | Derived1_1 | ControlFlowNode for unknown() | +| h_classes.py:42 | Derived2_1 | ControlFlowNode for Base() | +| h_classes.py:42 | Derived2_1 | ControlFlowNode for unknown() | +| h_classes.py:42 | Derived3_1 | ControlFlowNode for Base() | +| h_classes.py:42 | Derived3_1 | ControlFlowNode for unknown() | +| h_classes.py:42 | __name___0 | ControlFlowNode for Base() | +| h_classes.py:42 | __name___0 | ControlFlowNode for unknown() | +| h_classes.py:42 | __package___0 | ControlFlowNode for Base() | +| h_classes.py:42 | __package___0 | ControlFlowNode for unknown() | +| h_classes.py:42 | f_0 | ControlFlowNode for Base() | +| h_classes.py:42 | f_0 | ControlFlowNode for unknown() | +| h_classes.py:42 | k_1 | ControlFlowNode for Base() | +| h_classes.py:42 | k_1 | ControlFlowNode for unknown() | +| h_classes.py:42 | sys_1 | ControlFlowNode for Base() | +| h_classes.py:42 | sys_1 | ControlFlowNode for unknown() | +| h_classes.py:42 | thing_0 | ControlFlowNode for Base() | +| h_classes.py:42 | thing_0 | ControlFlowNode for unknown() | +| h_classes.py:45 | arg0_0 | Exit node for Function f | +| h_classes.py:45 | arg1_0 | Exit node for Function f | +| h_classes.py:45 | arg2_0 | Exit node for Function f | +| h_classes.py:48 | f_1 | ControlFlowNode for ClassExpr | +| h_classes.py:48 | m_0 | Exit node for Class D | +| h_classes.py:48 | n_0 | Exit node for Class D | +| h_classes.py:50 | f_2 | ControlFlowNode for f | +| h_classes.py:52 | arg1_0 | Exit node for Function n | +| h_classes.py:52 | self_0 | Exit node for Function n | +| j_convoluted_imports.py:0 | C_0 | Exit node for Module code.j_convoluted_imports | +| j_convoluted_imports.py:0 | __name___0 | Exit node for Module code.j_convoluted_imports | +| j_convoluted_imports.py:0 | __package___0 | Exit node for Module code.j_convoluted_imports | +| j_convoluted_imports.py:0 | moduleX_0 | Exit node for Module code.j_convoluted_imports | +| j_convoluted_imports.py:0 | module_0 | Exit node for Module code.j_convoluted_imports | +| j_convoluted_imports.py:0 | x_0 | Exit node for Module code.j_convoluted_imports | +| j_convoluted_imports.py:9 | f_0 | Exit node for Class C | +| j_convoluted_imports.py:9 | module2_0 | Exit node for Class C | +| j_convoluted_imports.py:13 | self_0 | Exit node for Function f | +| j_convoluted_imports.py:13 | x_0 | Exit node for Function f | +| j_convoluted_imports.py:17 | moduleX_0 | ControlFlowNode for moduleX | +| k_getsetattr.py:0 | C_0 | Exit node for Module code.k_getsetattr | +| k_getsetattr.py:0 | __name___0 | Exit node for Module code.k_getsetattr | +| k_getsetattr.py:0 | __package___0 | Exit node for Module code.k_getsetattr | +| k_getsetattr.py:0 | k_0 | Exit node for Module code.k_getsetattr | +| k_getsetattr.py:4 | meth1_0 | Exit node for Class C | +| k_getsetattr.py:4 | meth2_0 | Exit node for Class C | +| k_getsetattr.py:6 | self_4 | Exit node for Function meth1 | +| k_getsetattr.py:7 | self_0 | ControlFlowNode for self | +| k_getsetattr.py:8 | self_1 | ControlFlowNode for self | +| k_getsetattr.py:9 | self_2 | ControlFlowNode for self | +| k_getsetattr.py:10 | self_3 | ControlFlowNode for self | +| k_getsetattr.py:12 | self_6 | Exit node for Function meth2 | +| k_getsetattr.py:13 | self_0 | ControlFlowNode for self | +| k_getsetattr.py:14 | self_1 | ControlFlowNode for self | +| k_getsetattr.py:15 | self_2 | ControlFlowNode for self | +| k_getsetattr.py:16 | self_3 | ControlFlowNode for self | +| k_getsetattr.py:17 | self_4 | ControlFlowNode for self | +| k_getsetattr.py:18 | self_5 | ControlFlowNode for self | +| k_getsetattr.py:21 | c1_1 | Exit node for Function k | +| k_getsetattr.py:21 | c2_2 | Exit node for Function k | +| k_getsetattr.py:21 | c3_1 | Exit node for Function k | +| k_getsetattr.py:21 | cond_3 | Exit node for Function k | +| k_getsetattr.py:22 | C_1 | ControlFlowNode for C | +| k_getsetattr.py:23 | C_1 | ControlFlowNode for C | +| k_getsetattr.py:24 | C_1 | ControlFlowNode for C | +| k_getsetattr.py:25 | c1_0 | ControlFlowNode for c1 | +| k_getsetattr.py:26 | cond_0 | ControlFlowNode for cond | +| k_getsetattr.py:27 | c2_0 | ControlFlowNode for c2 | +| k_getsetattr.py:28 | c1_1 | ControlFlowNode for c1 | +| k_getsetattr.py:29 | c2_2 | ControlFlowNode for c2 | +| k_getsetattr.py:30 | c3_0 | ControlFlowNode for c3 | +| k_getsetattr.py:31 | c3_0 | ControlFlowNode for c3 | +| s_scopes.py:0 | C2_0 | Exit node for Module code.s_scopes | +| s_scopes.py:0 | __name___0 | Exit node for Module code.s_scopes | +| s_scopes.py:0 | __package___0 | Exit node for Module code.s_scopes | +| s_scopes.py:0 | f_0 | Exit node for Module code.s_scopes | +| s_scopes.py:0 | float_2 | Exit node for Module code.s_scopes | +| s_scopes.py:0 | i_0 | Exit node for Module code.s_scopes | +| s_scopes.py:0 | x_1 | Exit node for Module code.s_scopes | +| s_scopes.py:7 | f1_0 | Exit node for Class C2 | +| s_scopes.py:7 | f2_0 | Exit node for Class C2 | +| s_scopes.py:7 | float_2 | ControlFlowNode for ClassExpr | +| s_scopes.py:7 | float_2 | Exit node for Class C2 | +| s_scopes.py:7 | i1_0 | Exit node for Class C2 | +| s_scopes.py:7 | i2_0 | Exit node for Class C2 | +| s_scopes.py:7 | int_1 | Exit node for Class C2 | +| s_scopes.py:7 | s_0 | Exit node for Class C2 | +| s_scopes.py:7 | str_2 | Exit node for Class C2 | +| s_scopes.py:9 | int_0 | ControlFlowNode for int | +| s_scopes.py:10 | float_0 | ControlFlowNode for float | +| s_scopes.py:10 | float_3 | ControlFlowNode for float | +| s_scopes.py:18 | int_1 | ControlFlowNode for int | +| s_scopes.py:19 | str_2 | ControlFlowNode for str | +| s_scopes.py:20 | float_2 | ControlFlowNode for float | +| s_scopes.py:20 | float_3 | ControlFlowNode for float | +| s_scopes.py:22 | x_0 | ControlFlowNode for x | +| s_scopes.py:24 | float_2 | ControlFlowNode for float | diff --git a/python/ql/test/library-tests/PointsTo/new/SsaUses.ql b/python/ql/test/library-tests/PointsTo/new/SsaUses.ql new file mode 100644 index 000000000000..681ac79bc780 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/SsaUses.ql @@ -0,0 +1,9 @@ + +import python +import semmle.dataflow.SSA +import semmle.python.pointsto.PointsTo +import Util + +from EssaVariable var, ControlFlowNode use +where use = var.getAUse() +select locate(use.getLocation(), "abdeghjks_"), var.getRepresentation(), use.toString() diff --git a/python/ql/test/library-tests/PointsTo/new/TestEvaluate.expected b/python/ql/test/library-tests/PointsTo/new/TestEvaluate.expected new file mode 100644 index 000000000000..9bdee5cf24e1 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/TestEvaluate.expected @@ -0,0 +1,55 @@ +| b_condition.py:7 | Compare | true | x | NoneType None | +| b_condition.py:13 | Compare | false | x | NoneType None | +| b_condition.py:19 | UnaryExpr | true | x | NoneType None | +| b_condition.py:25 | x | false | x | NoneType None | +| b_condition.py:32 | UnaryExpr | false | x | int 1 | +| b_condition.py:36 | isinstance() | true | x | int 1 | +| b_condition.py:36 | isinstance() | true | x | int 7 | +| b_condition.py:71 | UnaryExpr | false | b | bool True | +| b_condition.py:77 | Compare | true | object | builtin-class object | +| b_condition.py:77 | Compare | true | t | builtin-class type | +| b_condition.py:82 | callable() | false | foo | bool True | +| b_condition.py:88 | x | false | x | NoneType None | +| b_condition.py:88 | y | false | y | NoneType None | +| b_condition.py:90 | x | false | x | NoneType None | +| b_condition.py:90 | y | false | y | NoneType None | +| b_condition.py:92 | x | false | x | NoneType None | +| b_condition.py:96 | y | false | y | NoneType None | +| b_condition.py:102 | UnaryExpr | false | a | a | +| b_condition.py:104 | UnaryExpr | false | a | a | +| c_tests.py:7 | Compare | true | x | NoneType None | +| c_tests.py:12 | x | false | x | int 0 | +| c_tests.py:12 | x | true | x | int 1 | +| c_tests.py:17 | Compare | false | x | int 1 | +| c_tests.py:17 | Compare | true | x | int 0 | +| c_tests.py:23 | len() | true | x | List | +| c_tests.py:23 | len() | true | x | Tuple | +| c_tests.py:26 | Compare | false | x | Tuple | +| c_tests.py:26 | Compare | true | x | List | +| c_tests.py:26 | Compare | true | x | Tuple | +| c_tests.py:29 | isinstance() | false | x | List | +| c_tests.py:29 | isinstance() | true | x | Tuple | +| c_tests.py:34 | Compare | true | Attribute | NoneType None | +| c_tests.py:39 | Attribute | false | Attribute | int 0 | +| c_tests.py:39 | Attribute | true | Attribute | int 1 | +| c_tests.py:44 | Compare | false | Attribute | int 1 | +| c_tests.py:44 | Compare | true | Attribute | int 0 | +| c_tests.py:50 | isinstance() | false | Attribute | List | +| c_tests.py:50 | isinstance() | true | Attribute | Tuple | +| c_tests.py:53 | Compare | false | Attribute | Tuple | +| c_tests.py:53 | Compare | true | Attribute | List | +| c_tests.py:53 | Compare | true | Attribute | Tuple | +| c_tests.py:60 | issubclass() | false | x | builtin-class type | +| c_tests.py:60 | issubclass() | true | x | builtin-class bool | +| c_tests.py:65 | hasattr() | false | x | builtin-class float | +| c_tests.py:65 | hasattr() | true | x | int 0 | +| c_tests.py:68 | callable() | false | x | int 0 | +| c_tests.py:68 | callable() | true | x | builtin-class float | +| c_tests.py:73 | x | true | x | int 1 | +| c_tests.py:73 | y | false | y | int 0 | +| c_tests.py:76 | x | true | x | int 1 | +| c_tests.py:76 | y | false | y | int 0 | +| c_tests.py:81 | b | true | b | bool True | +| c_tests.py:84 | UnaryExpr | false | b | bool True | +| c_tests.py:91 | x | false | x | NoneType None | +| c_tests.py:95 | UnaryExpr | true | x | NoneType None | diff --git a/python/ql/test/library-tests/PointsTo/new/TestEvaluate.ql b/python/ql/test/library-tests/PointsTo/new/TestEvaluate.ql new file mode 100644 index 000000000000..731b710d2c5e --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/TestEvaluate.ql @@ -0,0 +1,14 @@ + +import python +import semmle.python.pointsto.PointsTo +import semmle.python.pointsto.PointsToContext +import Util + + +from ControlFlowNode test, ControlFlowNode use, Object val, boolean eval, ClassObject cls, PointsToContext ctx +where +not use instanceof NameConstantNode and +not use.getNode() instanceof ImmutableLiteral and +PointsTo::points_to(use, ctx, val, cls, _) and +eval = PointsTo::test_evaluates_boolean(test, use, ctx, val, cls, _) +select locate(test.getLocation(), "bc"), test.getNode().toString(), eval.toString(), use.getNode().toString(), val.toString() diff --git a/python/ql/test/library-tests/PointsTo/new/Util.qll b/python/ql/test/library-tests/PointsTo/new/Util.qll new file mode 100644 index 000000000000..8e1d317cc68b --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/Util.qll @@ -0,0 +1,30 @@ +import python + +bindingset[which] +string locate(Location l, string which) { + exists(string file, int line | + file = l.getFile().getShortName() and + line = l.getStartLine() and + file.charAt(0) = which.charAt(_) and + file.charAt(1) = "_" and + result = file + ":" + line + ) +} + +string repr(Object o) { + /* Do not show `unknownValue()` to keep noise levels down. + * To show it add: + * `o = unknownValue() and result = "*UNKNOWN VALUE*"` + */ + not o instanceof StringObject and not o = undefinedVariable() and not o = theUnknownType() and + not o = theBoundMethodType() and result = o.toString() + or + o = undefinedVariable() and result = "*UNDEFINED*" + or + o = theUnknownType() and result = "*UNKNOWN TYPE*" + or + /* Work around differing names in 2/3 */ + result = "'" + o.(StringObject).getText() + "'" + or + o = theBoundMethodType() and result = "builtin-class method" +} diff --git a/python/ql/test/library-tests/PointsTo/new/VarUses.expected b/python/ql/test/library-tests/PointsTo/new/VarUses.expected new file mode 100644 index 000000000000..4fcc0a1a1dc1 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/VarUses.expected @@ -0,0 +1,444 @@ +| a_simple.py:0 | C | Exit node for Module code.a_simple | +| a_simple.py:0 | KeyError | Exit node for Module code.a_simple | +| a_simple.py:0 | __name__ | Exit node for Module code.a_simple | +| a_simple.py:0 | __package__ | Exit node for Module code.a_simple | +| a_simple.py:0 | dict | Exit node for Module code.a_simple | +| a_simple.py:0 | f | Exit node for Module code.a_simple | +| a_simple.py:0 | f1 | Exit node for Module code.a_simple | +| a_simple.py:0 | func | Exit node for Module code.a_simple | +| a_simple.py:0 | i1 | Exit node for Module code.a_simple | +| a_simple.py:0 | multi_loop | Exit node for Module code.a_simple | +| a_simple.py:0 | multi_loop_in_try | Exit node for Module code.a_simple | +| a_simple.py:0 | object | Exit node for Module code.a_simple | +| a_simple.py:0 | s | Exit node for Module code.a_simple | +| a_simple.py:0 | tuple | Exit node for Module code.a_simple | +| a_simple.py:0 | vararg_kwarg | Exit node for Module code.a_simple | +| a_simple.py:0 | with_definition | Exit node for Module code.a_simple | +| a_simple.py:3 | dict | ControlFlowNode for dict | +| a_simple.py:4 | tuple | ControlFlowNode for tuple | +| a_simple.py:11 | object | ControlFlowNode for object | +| a_simple.py:14 | d | Exit node for Function vararg_kwarg | +| a_simple.py:14 | t | Exit node for Function vararg_kwarg | +| a_simple.py:15 | t | ControlFlowNode for t | +| a_simple.py:16 | d | ControlFlowNode for d | +| a_simple.py:18 | seq | Exit node for Function multi_loop | +| a_simple.py:18 | x | Exit node for Function multi_loop | +| a_simple.py:18 | y | Exit node for Function multi_loop | +| a_simple.py:20 | seq | ControlFlowNode for seq | +| a_simple.py:21 | x | ControlFlowNode for x | +| a_simple.py:23 | x | Exit node for Function with_definition | +| a_simple.py:23 | y | Exit node for Function with_definition | +| a_simple.py:24 | x | ControlFlowNode for x | +| a_simple.py:25 | y | ControlFlowNode for y | +| a_simple.py:27 | p | Exit node for Function multi_loop_in_try | +| a_simple.py:27 | q | Exit node for Function multi_loop_in_try | +| a_simple.py:27 | x | Exit node for Function multi_loop_in_try | +| a_simple.py:29 | x | ControlFlowNode for x | +| a_simple.py:30 | p | ControlFlowNode for p | +| a_simple.py:31 | KeyError | ControlFlowNode for KeyError | +| a_simple.py:34 | args | Exit node for Function f | +| a_simple.py:34 | kwargs | Exit node for Function f | +| a_simple.py:35 | args | ControlFlowNode for args | +| a_simple.py:36 | kwargs | ControlFlowNode for kwargs | +| b_condition.py:0 | Exception | Exit node for Module code.b_condition | +| b_condition.py:0 | TypeError | Exit node for Module code.b_condition | +| b_condition.py:0 | __name__ | Exit node for Module code.b_condition | +| b_condition.py:0 | __package__ | Exit node for Module code.b_condition | +| b_condition.py:0 | callable | Exit node for Module code.b_condition | +| b_condition.py:0 | cond | Exit node for Module code.b_condition | +| b_condition.py:0 | double_attr_check | Exit node for Module code.b_condition | +| b_condition.py:0 | f | Exit node for Module code.b_condition | +| b_condition.py:0 | g | Exit node for Module code.b_condition | +| b_condition.py:0 | h | Exit node for Module code.b_condition | +| b_condition.py:0 | int | Exit node for Module code.b_condition | +| b_condition.py:0 | isinstance | Exit node for Module code.b_condition | +| b_condition.py:0 | k | Exit node for Module code.b_condition | +| b_condition.py:0 | list | Exit node for Module code.b_condition | +| b_condition.py:0 | loop | Exit node for Module code.b_condition | +| b_condition.py:0 | not_or_not | Exit node for Module code.b_condition | +| b_condition.py:0 | object | Exit node for Module code.b_condition | +| b_condition.py:0 | odasa6261 | Exit node for Module code.b_condition | +| b_condition.py:0 | seq | Exit node for Module code.b_condition | +| b_condition.py:0 | split_bool1 | Exit node for Module code.b_condition | +| b_condition.py:0 | thing | Exit node for Module code.b_condition | +| b_condition.py:0 | tuple | Exit node for Module code.b_condition | +| b_condition.py:0 | type | Exit node for Module code.b_condition | +| b_condition.py:0 | unknown | Exit node for Module code.b_condition | +| b_condition.py:0 | use | Exit node for Module code.b_condition | +| b_condition.py:0 | v2 | Exit node for Module code.b_condition | +| b_condition.py:4 | x | Exit node for Function f | +| b_condition.py:4 | y | Exit node for Function f | +| b_condition.py:5 | cond | ControlFlowNode for cond | +| b_condition.py:5 | unknown | ControlFlowNode for unknown | +| b_condition.py:7 | x | ControlFlowNode for x | +| b_condition.py:9 | use | ControlFlowNode for use | +| b_condition.py:9 | x | ControlFlowNode for x | +| b_condition.py:11 | cond | ControlFlowNode for cond | +| b_condition.py:11 | unknown | ControlFlowNode for unknown | +| b_condition.py:13 | x | ControlFlowNode for x | +| b_condition.py:15 | use | ControlFlowNode for use | +| b_condition.py:15 | x | ControlFlowNode for x | +| b_condition.py:17 | cond | ControlFlowNode for cond | +| b_condition.py:17 | unknown | ControlFlowNode for unknown | +| b_condition.py:19 | x | ControlFlowNode for x | +| b_condition.py:21 | use | ControlFlowNode for use | +| b_condition.py:21 | x | ControlFlowNode for x | +| b_condition.py:23 | cond | ControlFlowNode for cond | +| b_condition.py:23 | unknown | ControlFlowNode for unknown | +| b_condition.py:25 | x | ControlFlowNode for x | +| b_condition.py:26 | use | ControlFlowNode for use | +| b_condition.py:26 | x | ControlFlowNode for x | +| b_condition.py:27 | unknown | ControlFlowNode for unknown | +| b_condition.py:29 | use | ControlFlowNode for use | +| b_condition.py:29 | x | ControlFlowNode for x | +| b_condition.py:31 | cond | ControlFlowNode for cond | +| b_condition.py:31 | unknown | ControlFlowNode for unknown | +| b_condition.py:32 | x | ControlFlowNode for x | +| b_condition.py:34 | use | ControlFlowNode for use | +| b_condition.py:34 | x | ControlFlowNode for x | +| b_condition.py:36 | int | ControlFlowNode for int | +| b_condition.py:36 | isinstance | ControlFlowNode for isinstance | +| b_condition.py:36 | x | ControlFlowNode for x | +| b_condition.py:37 | use | ControlFlowNode for use | +| b_condition.py:37 | x | ControlFlowNode for x | +| b_condition.py:39 | __name__ | ControlFlowNode for thing() | +| b_condition.py:39 | __package__ | ControlFlowNode for thing() | +| b_condition.py:39 | double_attr_check | ControlFlowNode for thing() | +| b_condition.py:39 | f | ControlFlowNode for thing() | +| b_condition.py:39 | g | ControlFlowNode for thing() | +| b_condition.py:39 | h | ControlFlowNode for thing() | +| b_condition.py:39 | k | ControlFlowNode for thing() | +| b_condition.py:39 | loop | ControlFlowNode for thing() | +| b_condition.py:39 | not_or_not | ControlFlowNode for thing() | +| b_condition.py:39 | odasa6261 | ControlFlowNode for thing() | +| b_condition.py:39 | split_bool1 | ControlFlowNode for thing() | +| b_condition.py:39 | thing | ControlFlowNode for thing | +| b_condition.py:39 | v2 | ControlFlowNode for thing() | +| b_condition.py:41 | v2 | ControlFlowNode for v2 | +| b_condition.py:42 | v2 | ControlFlowNode for v2 | +| b_condition.py:43 | __name__ | ControlFlowNode for use() | +| b_condition.py:43 | __package__ | ControlFlowNode for use() | +| b_condition.py:43 | double_attr_check | ControlFlowNode for use() | +| b_condition.py:43 | f | ControlFlowNode for use() | +| b_condition.py:43 | g | ControlFlowNode for use() | +| b_condition.py:43 | h | ControlFlowNode for use() | +| b_condition.py:43 | k | ControlFlowNode for use() | +| b_condition.py:43 | loop | ControlFlowNode for use() | +| b_condition.py:43 | not_or_not | ControlFlowNode for use() | +| b_condition.py:43 | odasa6261 | ControlFlowNode for use() | +| b_condition.py:43 | split_bool1 | ControlFlowNode for use() | +| b_condition.py:43 | use | ControlFlowNode for use | +| b_condition.py:43 | v2 | ControlFlowNode for use() | +| b_condition.py:43 | v2 | ControlFlowNode for v2 | +| b_condition.py:44 | __name__ | ControlFlowNode for use() | +| b_condition.py:44 | __package__ | ControlFlowNode for use() | +| b_condition.py:44 | double_attr_check | ControlFlowNode for use() | +| b_condition.py:44 | f | ControlFlowNode for use() | +| b_condition.py:44 | g | ControlFlowNode for use() | +| b_condition.py:44 | h | ControlFlowNode for use() | +| b_condition.py:44 | k | ControlFlowNode for use() | +| b_condition.py:44 | loop | ControlFlowNode for use() | +| b_condition.py:44 | not_or_not | ControlFlowNode for use() | +| b_condition.py:44 | odasa6261 | ControlFlowNode for use() | +| b_condition.py:44 | split_bool1 | ControlFlowNode for use() | +| b_condition.py:44 | use | ControlFlowNode for use | +| b_condition.py:44 | v2 | ControlFlowNode for use() | +| b_condition.py:44 | v2 | ControlFlowNode for v2 | +| b_condition.py:50 | x | Exit node for Function g | +| b_condition.py:51 | x | ControlFlowNode for x | +| b_condition.py:52 | x | ControlFlowNode for x | +| b_condition.py:55 | seq | Exit node for Function loop | +| b_condition.py:55 | v | Exit node for Function loop | +| b_condition.py:56 | seq | ControlFlowNode for seq | +| b_condition.py:57 | v | ControlFlowNode for v | +| b_condition.py:58 | use | ControlFlowNode for use | +| b_condition.py:58 | v | ControlFlowNode for v | +| b_condition.py:61 | x | Exit node for Function double_attr_check | +| b_condition.py:61 | y | Exit node for Function double_attr_check | +| b_condition.py:62 | x | ControlFlowNode for x | +| b_condition.py:64 | y | ControlFlowNode for y | +| b_condition.py:65 | x | ControlFlowNode for x | +| b_condition.py:66 | seq | ControlFlowNode for seq | +| b_condition.py:66 | x | ControlFlowNode for x | +| b_condition.py:69 | b | Exit node for Function h | +| b_condition.py:70 | cond | ControlFlowNode for cond | +| b_condition.py:70 | unknown | ControlFlowNode for unknown | +| b_condition.py:71 | b | ControlFlowNode for b | +| b_condition.py:73 | b | ControlFlowNode for b | +| b_condition.py:75 | t | Exit node for Function k | +| b_condition.py:76 | type | ControlFlowNode for type | +| b_condition.py:77 | object | ControlFlowNode for object | +| b_condition.py:77 | t | ControlFlowNode for t | +| b_condition.py:78 | object | ControlFlowNode for object | +| b_condition.py:79 | t | ControlFlowNode for t | +| b_condition.py:79 | use | ControlFlowNode for use | +| b_condition.py:81 | bar | Exit node for Function odasa6261 | +| b_condition.py:81 | foo | Exit node for Function odasa6261 | +| b_condition.py:82 | callable | ControlFlowNode for callable | +| b_condition.py:82 | foo | ControlFlowNode for callable() | +| b_condition.py:82 | foo | ControlFlowNode for foo | +| b_condition.py:84 | foo | ControlFlowNode for foo | +| b_condition.py:84 | foo | ControlFlowNode for foo() | +| b_condition.py:87 | x | Exit node for Function split_bool1 | +| b_condition.py:87 | y | Exit node for Function split_bool1 | +| b_condition.py:88 | x | ControlFlowNode for x | +| b_condition.py:88 | y | ControlFlowNode for y | +| b_condition.py:90 | x | ControlFlowNode for x | +| b_condition.py:90 | y | ControlFlowNode for y | +| b_condition.py:92 | x | ControlFlowNode for x | +| b_condition.py:93 | use | ControlFlowNode for use | +| b_condition.py:93 | y | ControlFlowNode for y | +| b_condition.py:95 | use | ControlFlowNode for use | +| b_condition.py:95 | y | ControlFlowNode for y | +| b_condition.py:96 | y | ControlFlowNode for y | +| b_condition.py:97 | use | ControlFlowNode for use | +| b_condition.py:97 | x | ControlFlowNode for x | +| b_condition.py:99 | use | ControlFlowNode for use | +| b_condition.py:99 | x | ControlFlowNode for x | +| b_condition.py:101 | a | Exit node for Function not_or_not | +| b_condition.py:102 | a | ControlFlowNode for a | +| b_condition.py:102 | isinstance | ControlFlowNode for isinstance | +| b_condition.py:102 | list | ControlFlowNode for list | +| b_condition.py:102 | tuple | ControlFlowNode for tuple | +| b_condition.py:103 | TypeError | ControlFlowNode for TypeError | +| b_condition.py:104 | a | ControlFlowNode for a | +| b_condition.py:105 | a | ControlFlowNode for a | +| b_condition.py:106 | Exception | ControlFlowNode for Exception | +| d_globals.py:0 | D | Exit node for Module code.d_globals | +| d_globals.py:0 | Ugly | Exit node for Module code.d_globals | +| d_globals.py:0 | X | Exit node for Module code.d_globals | +| d_globals.py:0 | __name__ | Exit node for Module code.d_globals | +| d_globals.py:0 | __package__ | Exit node for Module code.d_globals | +| d_globals.py:0 | assign_global | Exit node for Module code.d_globals | +| d_globals.py:0 | cond | Exit node for Module code.d_globals | +| d_globals.py:0 | cond3 | Exit node for Module code.d_globals | +| d_globals.py:0 | dict | Exit node for Module code.d_globals | +| d_globals.py:0 | g1 | Exit node for Module code.d_globals | +| d_globals.py:0 | g2 | Exit node for Module code.d_globals | +| d_globals.py:0 | g3 | Exit node for Module code.d_globals | +| d_globals.py:0 | g4 | Exit node for Module code.d_globals | +| d_globals.py:0 | get_g4 | Exit node for Module code.d_globals | +| d_globals.py:0 | glob | Exit node for Module code.d_globals | +| d_globals.py:0 | init | Exit node for Module code.d_globals | +| d_globals.py:0 | j | Exit node for Module code.d_globals | +| d_globals.py:0 | k | Exit node for Module code.d_globals | +| d_globals.py:0 | list | Exit node for Module code.d_globals | +| d_globals.py:0 | modinit | Exit node for Module code.d_globals | +| d_globals.py:0 | object | Exit node for Module code.d_globals | +| d_globals.py:0 | other_cond | Exit node for Module code.d_globals | +| d_globals.py:0 | outer | Exit node for Module code.d_globals | +| d_globals.py:0 | redefine | Exit node for Module code.d_globals | +| d_globals.py:0 | set_g4 | Exit node for Module code.d_globals | +| d_globals.py:0 | set_g4_indirect | Exit node for Module code.d_globals | +| d_globals.py:0 | tuple | Exit node for Module code.d_globals | +| d_globals.py:0 | type | Exit node for Module code.d_globals | +| d_globals.py:0 | use_list_attribute | Exit node for Module code.d_globals | +| d_globals.py:0 | v3 | Exit node for Module code.d_globals | +| d_globals.py:0 | x | Exit node for Module code.d_globals | +| d_globals.py:0 | y | Exit node for Module code.d_globals | +| d_globals.py:0 | z | Exit node for Module code.d_globals | +| d_globals.py:2 | g1 | Exit node for Function j | +| d_globals.py:2 | g2 | Exit node for Function j | +| d_globals.py:2 | g3 | Exit node for Function j | +| d_globals.py:2 | g4 | Exit node for Function j | +| d_globals.py:2 | glob | Exit node for Function j | +| d_globals.py:2 | z | Exit node for Function j | +| d_globals.py:3 | dict | ControlFlowNode for dict | +| d_globals.py:3 | tuple | ControlFlowNode for tuple | +| d_globals.py:4 | dict | ControlFlowNode for dict | +| d_globals.py:6 | dict | ControlFlowNode for dict | +| d_globals.py:7 | tuple | ControlFlowNode for tuple | +| d_globals.py:8 | tuple | ControlFlowNode for tuple | +| d_globals.py:16 | g1 | Exit node for Function assign_global | +| d_globals.py:16 | g2 | Exit node for Function assign_global | +| d_globals.py:16 | g3 | Exit node for Function assign_global | +| d_globals.py:16 | g4 | Exit node for Function assign_global | +| d_globals.py:16 | glob | Exit node for Function assign_global | +| d_globals.py:16 | z | Exit node for Function assign_global | +| d_globals.py:19 | g1 | ControlFlowNode for g1 | +| d_globals.py:25 | g1 | Exit node for Function init | +| d_globals.py:25 | g2 | Exit node for Function init | +| d_globals.py:25 | g3 | Exit node for Function init | +| d_globals.py:25 | g4 | Exit node for Function init | +| d_globals.py:25 | glob | Exit node for Function init | +| d_globals.py:25 | z | Exit node for Function init | +| d_globals.py:29 | D | ControlFlowNode for init() | +| d_globals.py:29 | Ugly | ControlFlowNode for init() | +| d_globals.py:29 | X | ControlFlowNode for init() | +| d_globals.py:29 | __name__ | ControlFlowNode for init() | +| d_globals.py:29 | __package__ | ControlFlowNode for init() | +| d_globals.py:29 | assign_global | ControlFlowNode for init() | +| d_globals.py:29 | dict | ControlFlowNode for init() | +| d_globals.py:29 | g1 | ControlFlowNode for init() | +| d_globals.py:29 | g2 | ControlFlowNode for init() | +| d_globals.py:29 | g3 | ControlFlowNode for init() | +| d_globals.py:29 | g4 | ControlFlowNode for init() | +| d_globals.py:29 | get_g4 | ControlFlowNode for init() | +| d_globals.py:29 | glob | ControlFlowNode for init() | +| d_globals.py:29 | init | ControlFlowNode for init | +| d_globals.py:29 | init | ControlFlowNode for init() | +| d_globals.py:29 | j | ControlFlowNode for init() | +| d_globals.py:29 | k | ControlFlowNode for init() | +| d_globals.py:29 | modinit | ControlFlowNode for init() | +| d_globals.py:29 | outer | ControlFlowNode for init() | +| d_globals.py:29 | redefine | ControlFlowNode for init() | +| d_globals.py:29 | set_g4 | ControlFlowNode for init() | +| d_globals.py:29 | set_g4_indirect | ControlFlowNode for init() | +| d_globals.py:29 | tuple | ControlFlowNode for init() | +| d_globals.py:29 | use_list_attribute | ControlFlowNode for init() | +| d_globals.py:29 | x | ControlFlowNode for init() | +| d_globals.py:29 | y | ControlFlowNode for init() | +| d_globals.py:29 | z | ControlFlowNode for init() | +| d_globals.py:30 | g2 | ControlFlowNode for g2 | +| d_globals.py:35 | __init__ | Exit node for Class Ugly | +| d_globals.py:35 | meth | Exit node for Class Ugly | +| d_globals.py:35 | object | ControlFlowNode for object | +| d_globals.py:37 | g1 | Exit node for Function __init__ | +| d_globals.py:37 | g2 | Exit node for Function __init__ | +| d_globals.py:37 | g3 | Exit node for Function __init__ | +| d_globals.py:37 | g4 | Exit node for Function __init__ | +| d_globals.py:37 | glob | Exit node for Function __init__ | +| d_globals.py:37 | self | Exit node for Function __init__ | +| d_globals.py:37 | z | Exit node for Function __init__ | +| d_globals.py:41 | g1 | Exit node for Function meth | +| d_globals.py:41 | g2 | Exit node for Function meth | +| d_globals.py:41 | g3 | Exit node for Function meth | +| d_globals.py:41 | g4 | Exit node for Function meth | +| d_globals.py:41 | glob | Exit node for Function meth | +| d_globals.py:41 | self | Exit node for Function meth | +| d_globals.py:41 | z | Exit node for Function meth | +| d_globals.py:42 | g3 | ControlFlowNode for g3 | +| d_globals.py:47 | x | ControlFlowNode for x | +| d_globals.py:48 | cond | ControlFlowNode for cond | +| d_globals.py:51 | other_cond | ControlFlowNode for other_cond | +| d_globals.py:55 | cond3 | ControlFlowNode for cond3 | +| d_globals.py:59 | y | ControlFlowNode for y | +| d_globals.py:60 | v3 | ControlFlowNode for v3 | +| d_globals.py:62 | X | ControlFlowNode for ClassExpr | +| d_globals.py:62 | g3 | ControlFlowNode for ClassExpr | +| d_globals.py:62 | object | ControlFlowNode for object | +| d_globals.py:62 | v4 | Exit node for Class X | +| d_globals.py:62 | y | ControlFlowNode for ClassExpr | +| d_globals.py:62 | y | Exit node for Class X | +| d_globals.py:63 | y | ControlFlowNode for y | +| d_globals.py:64 | v3 | ControlFlowNode for v3 | +| d_globals.py:65 | X | ControlFlowNode for X | +| d_globals.py:66 | g3 | ControlFlowNode for g3 | +| d_globals.py:68 | type | ControlFlowNode for type | +| d_globals.py:70 | arg | Exit node for Function k | +| d_globals.py:70 | g1 | Exit node for Function k | +| d_globals.py:70 | g2 | Exit node for Function k | +| d_globals.py:70 | g3 | Exit node for Function k | +| d_globals.py:70 | g4 | Exit node for Function k | +| d_globals.py:70 | glob | Exit node for Function k | +| d_globals.py:70 | z | Exit node for Function k | +| d_globals.py:71 | type | ControlFlowNode for type | +| d_globals.py:75 | g1 | Exit node for Function get_g4 | +| d_globals.py:75 | g2 | Exit node for Function get_g4 | +| d_globals.py:75 | g3 | Exit node for Function get_g4 | +| d_globals.py:75 | g4 | Exit node for Function get_g4 | +| d_globals.py:75 | glob | Exit node for Function get_g4 | +| d_globals.py:75 | z | Exit node for Function get_g4 | +| d_globals.py:76 | g4 | ControlFlowNode for g4 | +| d_globals.py:77 | g1 | ControlFlowNode for set_g4() | +| d_globals.py:77 | g2 | ControlFlowNode for set_g4() | +| d_globals.py:77 | g3 | ControlFlowNode for set_g4() | +| d_globals.py:77 | g4 | ControlFlowNode for set_g4() | +| d_globals.py:77 | glob | ControlFlowNode for set_g4() | +| d_globals.py:77 | set_g4 | ControlFlowNode for set_g4 | +| d_globals.py:77 | z | ControlFlowNode for set_g4() | +| d_globals.py:78 | g4 | ControlFlowNode for g4 | +| d_globals.py:80 | g1 | Exit node for Function set_g4 | +| d_globals.py:80 | g2 | Exit node for Function set_g4 | +| d_globals.py:80 | g3 | Exit node for Function set_g4 | +| d_globals.py:80 | g4 | Exit node for Function set_g4 | +| d_globals.py:80 | glob | Exit node for Function set_g4 | +| d_globals.py:80 | z | Exit node for Function set_g4 | +| d_globals.py:81 | g1 | ControlFlowNode for set_g4_indirect() | +| d_globals.py:81 | g2 | ControlFlowNode for set_g4_indirect() | +| d_globals.py:81 | g3 | ControlFlowNode for set_g4_indirect() | +| d_globals.py:81 | g4 | ControlFlowNode for set_g4_indirect() | +| d_globals.py:81 | glob | ControlFlowNode for set_g4_indirect() | +| d_globals.py:81 | set_g4_indirect | ControlFlowNode for set_g4_indirect | +| d_globals.py:81 | z | ControlFlowNode for set_g4_indirect() | +| d_globals.py:83 | g1 | Exit node for Function set_g4_indirect | +| d_globals.py:83 | g2 | Exit node for Function set_g4_indirect | +| d_globals.py:83 | g3 | Exit node for Function set_g4_indirect | +| d_globals.py:83 | g4 | Exit node for Function set_g4_indirect | +| d_globals.py:83 | glob | Exit node for Function set_g4_indirect | +| d_globals.py:83 | z | Exit node for Function set_g4_indirect | +| d_globals.py:87 | object | ControlFlowNode for object | +| d_globals.py:92 | modinit | ControlFlowNode for modinit | +| d_globals.py:95 | g1 | Exit node for Function outer | +| d_globals.py:95 | g2 | Exit node for Function outer | +| d_globals.py:95 | g3 | Exit node for Function outer | +| d_globals.py:95 | g4 | Exit node for Function outer | +| d_globals.py:95 | glob | Exit node for Function outer | +| d_globals.py:95 | inner | Exit node for Function outer | +| d_globals.py:95 | otherInner | Exit node for Function outer | +| d_globals.py:95 | z | Exit node for Function outer | +| d_globals.py:96 | g1 | Exit node for Function inner | +| d_globals.py:96 | g2 | Exit node for Function inner | +| d_globals.py:96 | g3 | Exit node for Function inner | +| d_globals.py:96 | g4 | Exit node for Function inner | +| d_globals.py:96 | glob | Exit node for Function inner | +| d_globals.py:96 | z | Exit node for Function inner | +| d_globals.py:99 | glob | ControlFlowNode for glob | +| d_globals.py:101 | g1 | Exit node for Function otherInner | +| d_globals.py:101 | g2 | Exit node for Function otherInner | +| d_globals.py:101 | g3 | Exit node for Function otherInner | +| d_globals.py:101 | g4 | Exit node for Function otherInner | +| d_globals.py:101 | glob | Exit node for Function otherInner | +| d_globals.py:101 | z | Exit node for Function otherInner | +| d_globals.py:102 | glob | ControlFlowNode for glob | +| d_globals.py:104 | g1 | ControlFlowNode for inner() | +| d_globals.py:104 | g2 | ControlFlowNode for inner() | +| d_globals.py:104 | g3 | ControlFlowNode for inner() | +| d_globals.py:104 | g4 | ControlFlowNode for inner() | +| d_globals.py:104 | glob | ControlFlowNode for inner() | +| d_globals.py:104 | inner | ControlFlowNode for inner | +| d_globals.py:104 | z | ControlFlowNode for inner() | +| d_globals.py:107 | g1 | Exit node for Function redefine | +| d_globals.py:107 | g2 | Exit node for Function redefine | +| d_globals.py:107 | g3 | Exit node for Function redefine | +| d_globals.py:107 | g4 | Exit node for Function redefine | +| d_globals.py:107 | glob | Exit node for Function redefine | +| d_globals.py:107 | z | Exit node for Function redefine | +| d_globals.py:109 | z | ControlFlowNode for z | +| d_globals.py:111 | z | ControlFlowNode for z | +| d_globals.py:112 | glob | ControlFlowNode for glob | +| d_globals.py:114 | glob | ControlFlowNode for glob | +| d_globals.py:118 | __init__ | Exit node for Class D | +| d_globals.py:118 | foo | Exit node for Class D | +| d_globals.py:118 | object | ControlFlowNode for object | +| d_globals.py:120 | g1 | Exit node for Function __init__ | +| d_globals.py:120 | g2 | Exit node for Function __init__ | +| d_globals.py:120 | g3 | Exit node for Function __init__ | +| d_globals.py:120 | g4 | Exit node for Function __init__ | +| d_globals.py:120 | glob | Exit node for Function __init__ | +| d_globals.py:120 | self | Exit node for Function __init__ | +| d_globals.py:120 | z | Exit node for Function __init__ | +| d_globals.py:123 | g1 | Exit node for Function foo | +| d_globals.py:123 | g2 | Exit node for Function foo | +| d_globals.py:123 | g3 | Exit node for Function foo | +| d_globals.py:123 | g4 | Exit node for Function foo | +| d_globals.py:123 | glob | Exit node for Function foo | +| d_globals.py:123 | self | Exit node for Function foo | +| d_globals.py:123 | z | Exit node for Function foo | +| d_globals.py:124 | dict | ControlFlowNode for dict | +| d_globals.py:126 | g1 | Exit node for Function use_list_attribute | +| d_globals.py:126 | g2 | Exit node for Function use_list_attribute | +| d_globals.py:126 | g3 | Exit node for Function use_list_attribute | +| d_globals.py:126 | g4 | Exit node for Function use_list_attribute | +| d_globals.py:126 | glob | Exit node for Function use_list_attribute | +| d_globals.py:126 | l | Exit node for Function use_list_attribute | +| d_globals.py:126 | z | Exit node for Function use_list_attribute | +| d_globals.py:128 | g1 | ControlFlowNode for Attribute() | +| d_globals.py:128 | g2 | ControlFlowNode for Attribute() | +| d_globals.py:128 | g3 | ControlFlowNode for Attribute() | +| d_globals.py:128 | g4 | ControlFlowNode for Attribute() | +| d_globals.py:128 | glob | ControlFlowNode for Attribute() | +| d_globals.py:128 | l | ControlFlowNode for l | +| d_globals.py:128 | list | ControlFlowNode for list | +| d_globals.py:128 | z | ControlFlowNode for Attribute() | +| d_globals.py:129 | l | ControlFlowNode for l | diff --git a/python/ql/test/library-tests/PointsTo/new/VarUses.ql b/python/ql/test/library-tests/PointsTo/new/VarUses.ql new file mode 100644 index 000000000000..a8b8b276d474 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/VarUses.ql @@ -0,0 +1,9 @@ + +import python +import semmle.dataflow.SSA +import semmle.python.pointsto.PointsTo +import Util + +from SsaSourceVariable var, ControlFlowNode use +where use = var.getAUse() or var.hasRefinement(use, _) +select locate(use.getLocation(), "abd"), var.getName(), use.toString() diff --git a/python/ql/test/library-tests/PointsTo/new/code/__init__.py b/python/ql/test/library-tests/PointsTo/new/code/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/PointsTo/new/code/a_simple.py b/python/ql/test/library-tests/PointsTo/new/code/a_simple.py new file mode 100644 index 000000000000..e3eaf6b3f284 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/a_simple.py @@ -0,0 +1,36 @@ + +f1 = 1.0 +dict +tuple +i1 = 0 +s = () + +def func(): + pass + +class C(object): + pass + +def vararg_kwarg(*t, **d): + t + d + +def multi_loop(seq): + x = None + for x, y in seq: + x + +def with_definition(x): + with x as y: + y + +def multi_loop_in_try(x): + try: # This causes additional exception edges, such that: + for p, q in x: # `x` and `p` are not in the same BB. + p + except KeyError: + pass + +def f(*args, **kwargs): + not args[0] + not kwargs["x"] diff --git a/python/ql/test/library-tests/PointsTo/new/code/b_condition.py b/python/ql/test/library-tests/PointsTo/new/code/b_condition.py new file mode 100644 index 000000000000..7574955ca96e --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/b_condition.py @@ -0,0 +1,108 @@ + +#Edge guards, aka pi-nodes. + +def f(y): + x = unknown() if cond else None + + if x is None: + x = 7 + use(x) + + x = unknown() if cond else None + + if x is not None: + x = 7 + use(x) + + x = unknown() if cond else None + + if not x: + x = None + use(x) + + x = unknown() if cond else None + + x = x if x else 1 + use(x) + if unknown(): + x = 1 + use(x) + + x = unknown() if cond else 1 + if not x: #Negation + x = 7 + use(x) + + assert isinstance(x, int) + use(x) + +v2 = thing() + +v2.x = 1 +if v2.y is not None: + use(v2.x) + use(v2.y) + +#A home for pi and phi-nodes +pass + + +def g(x): + if x: + x + +#Dead pi- and phi-nodes +def loop(seq): + for v in seq: + if v: + use(v) + +#This was causing the sanity check to fail, +def double_attr_check(x, y): + if x.b == 3: + return + if y: + if (x.a == 0 and + x.a in seq): + return + +def h(): + b = unknown() if cond else True + if not b: + b = 7 + return b + +def k(): + t = type + if t is not object: + t = object + use(t) + +def odasa6261(foo=True): + if callable(foo): + def bar(): + return foo() + +#Splittings with boolean expressions: +def split_bool1(x=None,y=None): + if x and y: + raise + if not (x or y): + raise + if x: + use(y) + else: + use(y) + if y: + use(x) + else: + use(x) + +def not_or_not(*a): + if not isinstance(a, (tuple, list)): + raise TypeError() + if (not a or + not a[0]): + raise Exception() + "Hello" + diff --git a/python/ql/test/library-tests/PointsTo/new/code/c_tests.py b/python/ql/test/library-tests/PointsTo/new/code/c_tests.py new file mode 100644 index 000000000000..206157c0c686 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/c_tests.py @@ -0,0 +1,101 @@ + +#Edge guards, aka pi-nodes. + +def f(y): + x = unknown() if cond else None + + if x is None: + pass + + x = 0 if cond else 1 + + if x: + pass + + x = 0 if cond else 1 + + if x == 0: + pass + + + x = ((1,2) if cond else (1,2,3)) if unknown() else [1,2] + + if len(x): + pass + + if len(x) == 2: + pass + + if isinstance(x, tuple): + pass + + y.a = unknown() if cond else None + + if y.a is None: + pass + + y.a = 0 if cond else 1 + + if y.a: + pass + + y.a = 0 if cond else 1 + + if y.a == 0: + pass + + + y.a = ((1,2) if cond else (1,2,3)) if unknown() else [1,2] + + if isinstance(y.a, tuple): + pass + + if len(y.a) == 2: + pass + +def others(x): + + x = bool if cond else type + + if issubclass(x, int): + pass + + x = 0 if cond else float + + if hasattr(x, "bit_length"): + pass + + if callable(x): + pass + +def compound(x=1, y=0): + + if x or y: + x + y + + if x and y: + x + y + +def h(): + b = unknown() if cond else True + if b: + pass + b = unknown() if cond else True + if not b: + pass + + if unknown() == 3: + pass + + x = unknown() if cond else None + if x: + pass + + x = unknown() if cond else None + if not x: + pass + +def complex_test(x): # Was failing sanity check. + if not (foo(x) and bar(x)): + use(x) + pass diff --git a/python/ql/test/library-tests/PointsTo/new/code/d_globals.py b/python/ql/test/library-tests/PointsTo/new/code/d_globals.py new file mode 100644 index 000000000000..72a063b2a75f --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/d_globals.py @@ -0,0 +1,130 @@ + +def j(): + return tuple, dict +dict +dict = 7 +dict +tuple = tuple +tuple + + + + +#Global assignment in local scope +g1 = None + +def assign_global(): + global g1 + g1 = 101 + return g1 # Cannot be None + +#Assignment in local scope, but called from module level + +g2 = None + +def init(): + global g2 + g2 = 102 + +init() +g2 # Cannot be None + +#Global set in init method +g3 = None + +class Ugly(object): + + def __init__(self): + global g3 + g3 = 103 + + def meth(self): + return g3 # Cannot be None + +#Redefine +x = 0 +x = 1 +x +if cond: + x = 3 + +if other_cond: + y = 1 +else: + y = 2 + if cond3: + pass + else: + pass +y +v3 + +class X(object): + y = y + v4 = v3 + X # Undefined + g3 + +type + +def k(arg): + type + +g4 = None + +def get_g4(): + if not g4: + set_g4() + return g4 # Cannot be None + +def set_g4(): + set_g4_indirect() + +def set_g4_indirect(): + global g4 + g4 = False + +class modinit(object): #ODASA-5486 + + global z + z = 0 + +del modinit + +#ODASA-4688 +def outer(): + def inner(): + global glob + glob = 100 + return glob + + def otherInner(): + return glob + + inner() + + +def redefine(): + global z, glob + z + z = 1 + z + glob + glob = 50 + glob + + + +class D(object): + + def __init__(self): + pass + + def foo(self): + return dict + +def use_list_attribute(): + l = [] + list.append(l, 0) + return l + diff --git a/python/ql/test/library-tests/PointsTo/new/code/e_temporal.py b/python/ql/test/library-tests/PointsTo/new/code/e_temporal.py new file mode 100644 index 000000000000..c71154f91d99 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/e_temporal.py @@ -0,0 +1,12 @@ + +import sys + +def f(): + len(sys.argv) > 3 # Should be defined, as call to f() precedes import of sys. + #The return is completely unconditional, so we can safely infer that calls to f() return 1. + return 1 + +def g(arg): + return arg + +x = g(f()) diff --git a/python/ql/test/library-tests/PointsTo/new/code/f_finally.py b/python/ql/test/library-tests/PointsTo/new/code/f_finally.py new file mode 100644 index 000000000000..bfc42c08e329 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/f_finally.py @@ -0,0 +1,11 @@ +class Queue(object): + + def close(self): + self._closed = True + try: + self._reader.close() + finally: + close = self._close + if close: + self._close = None + close() # FP was here: None on exceptional branch diff --git a/python/ql/test/library-tests/PointsTo/new/code/g_class_init.py b/python/ql/test/library-tests/PointsTo/new/code/g_class_init.py new file mode 100644 index 000000000000..6fc385c0b248 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/g_class_init.py @@ -0,0 +1,54 @@ + +#Convoluted object initialisation and self attribute use. +class C(object): + + def __init__(self): + self._init() + self.x = 1 + + def _init(self): + self.y = 2 + self._init2() + + def _init2(self): + self.z = 3 + + def method(self): + use(self.x) + if isinstance(self.y, int): + use(self.y) + use(self.z) + pass # Give phi nodes a location + + +class Oddities(object): + + int = int + float = float + l = len + h = hash + + +class D(object): + + def __init__(self): + super(D, self).x + return super(D, self).__init__() + + + +#ODASA-4519 +#OK as we are using identity tests for unique objects +V2 = "v2" +V3 = "v3" + +class E(object): + def __init__(self, c): + if c: + self.version = V2 + else: + self.version = V3 + + def meth(self): + if self.version is V2: #FP here. + pass diff --git a/python/ql/test/library-tests/PointsTo/new/code/h_classes.py b/python/ql/test/library-tests/PointsTo/new/code/h_classes.py new file mode 100644 index 000000000000..c5077942c0d4 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/h_classes.py @@ -0,0 +1,54 @@ +import sys + +class C(object): + + x = 'C_x' + + def __init__(self): + self.y = 'c_y' + +type(C()) +type(sys) +type(name, (object,), {}) + +def k(arg): + type(C()) + type(sys) + type(arg) + type(name, (object,), {}) + + +#ODASA-3263 +#Django does this +class Base(object): + + def __init__(self, choice): + if choice == 1: + self.__class__ = Derived1 + elif choice == 2: + self.__class__ = Derived2 + else: + self.__class__ = Derived3 + +class Derived1(Base): + pass + +class Derived2(Base): + pass + +class Derived3(Base): + pass + +thing = Base(unknown()) + + +def f(arg0, arg1, arg2): + pass + +class D(object): + + m = f #Use function as a method. + + def n(self, arg1): + pass + diff --git a/python/ql/test/library-tests/PointsTo/new/code/i_imports.py b/python/ql/test/library-tests/PointsTo/new/code/i_imports.py new file mode 100644 index 000000000000..5c8bc52b7b6e --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/i_imports.py @@ -0,0 +1,38 @@ + + +a = 1 +b = 2 +c = 3 + +from .xyz import * +from . import xyz +xyz.x +z +a + +from sys import argv +#Check that points-to has inserted origin +argv + +import sys +sys.argv + + + + +import code.package.x +code.package.x + + +from code.test_package import * +# https://bugs.python.org/issue18602 +import _io +StringIO = _io.StringIO +BytesIO = _io.BytesIO + +import io +StringIO = io.StringIO +BytesIO = io.BytesIO + +import code.n_nesting +code.n_nesting.f2() diff --git a/python/ql/test/library-tests/PointsTo/new/code/j_convoluted_imports.py b/python/ql/test/library-tests/PointsTo/new/code/j_convoluted_imports.py new file mode 100644 index 000000000000..f22dd560be3a --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/j_convoluted_imports.py @@ -0,0 +1,17 @@ + +from code.package \ +import module + +from code.package \ +import x +#Should work correctly in nested scopes as well. + +class C(object): + + from code.package import module2 + + def f(self): + from code.package import x + +from code.package import moduleX +moduleX.Y diff --git a/python/ql/test/library-tests/PointsTo/new/code/k_getsetattr.py b/python/ql/test/library-tests/PointsTo/new/code/k_getsetattr.py new file mode 100644 index 000000000000..cd9604f7c7e3 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/k_getsetattr.py @@ -0,0 +1,31 @@ + +#Make sure that we handle getattr and setattr as well as they are needed for protobuf stubs. + +class C(object): + + def meth1(self): + setattr(self, "a", 0) + setattr(self, "b", 1) + getattr(self, "a") + getattr(self, "c") + + def meth2(self): + setattr(self, "a", 7.0) + setattr(self, "c", 2) + self.meth1() + getattr(self, "a") + getattr(self, "b") + getattr(self, "c") + +#Locally redefined attribute +def k(cond): + c1 = C() + c2 = C() + c3 = C() + c1.a = 10 + if cond: + c2.a = 20 + c1.a + c2.a + c3.a + c3.a = 30 diff --git a/python/ql/test/library-tests/PointsTo/new/code/l_calls.py b/python/ql/test/library-tests/PointsTo/new/code/l_calls.py new file mode 100644 index 000000000000..d49f373cec42 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/l_calls.py @@ -0,0 +1,26 @@ + + +def foo(x = []): + return x.append("x") + +def bar(x = []): + return len(x) + +foo() +bar() + +class Owner(object): + + @classmethod + def cm(cls, arg): + return cls + + @classmethod + def cm2(cls, arg): + return arg + + #Normal method + def m(self): + a = self.cm(0) + return a.cm2(1) + diff --git a/python/ql/test/library-tests/PointsTo/new/code/m_attributes.py b/python/ql/test/library-tests/PointsTo/new/code/m_attributes.py new file mode 100644 index 000000000000..1ac04de0bfd9 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/m_attributes.py @@ -0,0 +1,13 @@ + + +class C(object): + + def __init__(self, a=17): + self.a = a + + def foo(self, other): + self.a + other.a + +C().foo(C()) +C().foo(C(100)) diff --git a/python/ql/test/library-tests/PointsTo/new/code/n_nesting.py b/python/ql/test/library-tests/PointsTo/new/code/n_nesting.py new file mode 100644 index 000000000000..c3c630e55cdb --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/n_nesting.py @@ -0,0 +1,34 @@ + +# Guarded inner closure creation +# See ODASA-6212 +# TO DO: +# 1. Split on tests that control closure creation +# 2. Link scope-entry definition at inner scope entry +# to the corresponding exit definition. +def foo(compile_ops=True): + if callable(compile_ops): + def inner(node_def): + return compile_ops(node_def) + else: + def inner(node_def): + return compile_ops(node_def) + attrs = { + "inner": inner + } + return attrs + +#Track globals across deeply nested calls-- ODASA-6673 + +def f1(): + C.flag = 1 # Sufficiently deeply nested that we won't track `C` to here in the import context +def f2(): + f1() +def f3(): + f2() +def f4(): + f3() +class C(object): pass +f4() +class D(C): # But we should track `C` to here even though we can't track all the way down to `f1` + pass +C = 1 diff --git a/python/ql/test/library-tests/PointsTo/new/code/o_no_returns.py b/python/ql/test/library-tests/PointsTo/new/code/o_no_returns.py new file mode 100644 index 000000000000..0ca6e48c3cba --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/o_no_returns.py @@ -0,0 +1,26 @@ +#Test for ODASA-6418 + +import sys + +def bar(cond): + if cond: + fail("cond true") + + +def fail(message, *args): + write('Error:', message % args, file=sys.stderr) + sys.exit(1) + +def foo(cond): + bar() + +# To get the FP result reported in ODASA-6418, the following must hold: +#bar must be called directly (not transitively) from the module scope +#bar must precede fail +#The call to bar must follow fail +bar(unknown()) + +#The following do not trigger the bug +#foo(unknown()) +#pass + diff --git a/python/ql/test/library-tests/PointsTo/new/code/p_decorators.py b/python/ql/test/library-tests/PointsTo/new/code/p_decorators.py new file mode 100644 index 000000000000..d06f14f988bf --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/p_decorators.py @@ -0,0 +1,34 @@ + + +def simple(func): + func.__annotation__ = "Hello" + return func + +@simple +def foo(): + pass + +def complex(msg): + def annotate(func): + func.__annotation__ = msg + return func + return annotate + +@complex("Hi") +def bar(): + pass + +foo +bar + +class C(object): + + @staticmethod + def smeth(arg0, arg1): + arg0 + arg1 + + @classmethod + def cmeth(cls, arg0): + cls + arg0 diff --git a/python/ql/test/library-tests/PointsTo/new/code/package/__init__.py b/python/ql/test/library-tests/PointsTo/new/code/package/__init__.py new file mode 100644 index 000000000000..6a76e9ee942d --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/package/__init__.py @@ -0,0 +1,15 @@ +from .module \ +import module + +from . import module2 as module3 +module2 = 7 +from . import module2 as module4 +from . import module3 as module5 +from code.package import moduleX + +#We should now have: +#module2 = 7 +#module3 = package.module2 +#module4 = 7 +#module5 = package.module2 +#moduleX = package.moduleX diff --git a/python/ql/test/library-tests/PointsTo/new/code/package/module.py b/python/ql/test/library-tests/PointsTo/new/code/package/module.py new file mode 100644 index 000000000000..008b713d67ef --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/package/module.py @@ -0,0 +1,3 @@ + +def module(args): + pass diff --git a/python/ql/test/library-tests/PointsTo/new/code/package/module2.py b/python/ql/test/library-tests/PointsTo/new/code/package/module2.py new file mode 100644 index 000000000000..3aea0c58ce5d --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/package/module2.py @@ -0,0 +1 @@ +x = 0 diff --git a/python/ql/test/library-tests/PointsTo/new/code/package/moduleX.py b/python/ql/test/library-tests/PointsTo/new/code/package/moduleX.py new file mode 100644 index 000000000000..3b39b8c0985c --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/package/moduleX.py @@ -0,0 +1,2 @@ +class Y(object): + pass \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/new/code/package/x.py b/python/ql/test/library-tests/PointsTo/new/code/package/x.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/PointsTo/new/code/q_super.py b/python/ql/test/library-tests/PointsTo/new/code/q_super.py new file mode 100644 index 000000000000..174f3227dbb6 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/q_super.py @@ -0,0 +1,76 @@ +class Base2(object): + + def __init__(self): + super(Base2, self).__init__() + + + +class Derived4(Base2): + + def __init__(self): + super(Base2, self) + return super(Derived4, self).__init__() + +class Base1(object): + + def meth(self): + return 7 + +class Derived1(Base1): + + def meth(self): + return super(Derived1, self).meth() + +class Derived2(Derived1): + + def meth(self): + return super(Derived2, self).meth() + +class Derived5(Derived1): + + def meth(self): + return super(Derived5, self).meth() + +#Incorrect use of super() +class Wrong1(Derived5, Derived2): + + def meth(self): + return super(Derived5, self).meth() + +#ODASA-5799 +class DA(object): + + def __init__(self): + do_something() + +class DB(DA): + + class DC(DA): + + def __init__(self): + sup = super(DB.DC, self) + sup.__init__() + +#Simpler variants +class DD(DA): + + def __init__(self): + sup = super(DD, self) + sup.__init__() + +class DE(DA): + + class DF(DA): + + def __init__(self): + super(DE.DF, self).__init__() + +class N(object): + pass + +class M(N): + + def __init__(self): + s = super(M, self) + i = s.__init__ + i() diff --git a/python/ql/test/library-tests/PointsTo/new/code/r_regressions.py b/python/ql/test/library-tests/PointsTo/new/code/r_regressions.py new file mode 100644 index 000000000000..5579de3da8e3 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/r_regressions.py @@ -0,0 +1,98 @@ +#Assorted regressions and test cases + +# FP for None spotted during development +# in multiprocessing/queue.py +class Queue(object): + + def __init__(self): + + self._after_fork() + + def _after_fork(self): + self._closed = False + self._close = None + + def close(self): + self._closed = True + try: + self._reader.close() + finally: + close = self._close + if close: + self._close = None + close() # FP was here: None on exceptional branch + + +#ODASA-5018 +def f(x,y=None, z=0): + if ( + x + and + y + ) or ( + y + and + not + z + ): + #y cannot be None here. + use(y) + +#from Ansible +def find_library(name): + [data, _] = x() + return data + +def fail(msg): + pass + +class C(object): + + def fail(self, msg): + fail(msg) + +#The following challenge is provided for us by Django... + +# The challenge here is that the decorator returned by this functions returns a different object +# depending on whether its argument is a class or not. +def method_decorator(decorator, name=''): + # Original django comment and docstring removed. + + def _dec(obj): + is_class = isinstance(obj, type) + if is_class: + do_validation() + else: + func = obj + + def _wrapper(self, *args, **kwargs): + #Doesn't matter what this does. + pass + + if is_class: + setattr(obj, name, _wrapper) + return obj # If obj is a class, we return it. + + return _wrapper # Otherwise we return the wrapper function. + + return _dec + +def deco(func): + def _wrapper(*args, **kwargs): + return True + return _wrapper + +@method_decorator(deco, "method") +class TestFirst(object): + def method(self): + return "hello world" + +TestFirst().method() # TestFirst here should be the class, not the wrapper function... + + +import sys + +_names = sys.builtin_module_names + +if 'time' in _names: + import time as t diff --git a/python/ql/test/library-tests/PointsTo/new/code/s_scopes.py b/python/ql/test/library-tests/PointsTo/new/code/s_scopes.py new file mode 100644 index 000000000000..ca6a4796a921 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/s_scopes.py @@ -0,0 +1,24 @@ + +#Global or builtin +if a: + float = True +pass + +class C2(object): + + i1 = int + f1 = float + #local + int = 0 + if b: + #local or builtin + str = 1.0 + #local, global or builtin + float = None + i2 = int + s = str + f2 = float + +x = x +i = int +f = float diff --git a/python/ql/test/library-tests/PointsTo/new/code/t_type.py b/python/ql/test/library-tests/PointsTo/new/code/t_type.py new file mode 100644 index 000000000000..2cbca18846f7 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/t_type.py @@ -0,0 +1,16 @@ +import sys + +class C(object): + pass + +type(C()) +type(sys) +from module import unknown +type(unknown) +type(name, (object,), {}) + +def k(arg): + type(C()) + type(sys) + type(arg) + type(name, (object,), {}) diff --git a/python/ql/test/library-tests/PointsTo/new/code/test_package/__init__.py b/python/ql/test/library-tests/PointsTo/new/code/test_package/__init__.py new file mode 100644 index 000000000000..0000c542f77a --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/test_package/__init__.py @@ -0,0 +1,3 @@ +from .module1 import * +from .module2 import * +import sys \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/new/code/test_package/module1.py b/python/ql/test/library-tests/PointsTo/new/code/test_package/module1.py new file mode 100644 index 000000000000..19bd2408d569 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/test_package/module1.py @@ -0,0 +1,6 @@ +__all__ = [ 'p', 'q', 'r' ] + +p = 1 +q = 2 +r = 3 +s = 4 diff --git a/python/ql/test/library-tests/PointsTo/new/code/test_package/module2.py b/python/ql/test/library-tests/PointsTo/new/code/test_package/module2.py new file mode 100644 index 000000000000..126afeb52042 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/test_package/module2.py @@ -0,0 +1,6 @@ +__all__ = [ 'r', 's'] + +p = [] +q = () +r = {} +s = None diff --git a/python/ql/test/library-tests/PointsTo/new/code/u_paired_values.py b/python/ql/test/library-tests/PointsTo/new/code/u_paired_values.py new file mode 100644 index 000000000000..5c6dabea3613 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/u_paired_values.py @@ -0,0 +1,15 @@ + +def return_if_true(cond, val): + if cond: + return val + raise Exception() + +def test(cond): + x = return_if_true(True, 1) if cond else return_if_true(False, 2) + return x + +y = test(True) +y + +z = test(False) +z diff --git a/python/ql/test/library-tests/PointsTo/new/code/xyz.py b/python/ql/test/library-tests/PointsTo/new/code/xyz.py new file mode 100644 index 000000000000..392054917df4 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/code/xyz.py @@ -0,0 +1,4 @@ + +x = 1.0 +y = 2.0 +z = 3.0 diff --git a/python/ql/test/library-tests/PointsTo/new/options b/python/ql/test/library-tests/PointsTo/new/options new file mode 100644 index 000000000000..8e16f310b520 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/options @@ -0,0 +1,2 @@ +semmle-extractor-options: --max-import-depth=4 +optimize: true diff --git a/python/ql/test/library-tests/PointsTo/new/test.py b/python/ql/test/library-tests/PointsTo/new/test.py new file mode 100644 index 000000000000..04176e98a741 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/new/test.py @@ -0,0 +1 @@ +from code import * diff --git a/python/ql/test/library-tests/PointsTo/returns/Test.expected b/python/ql/test/library-tests/PointsTo/returns/Test.expected new file mode 100644 index 000000000000..1bffc0d741cb --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/returns/Test.expected @@ -0,0 +1,10 @@ +| Function f | builtin-class NoneType | +| Function f | builtin-class int | +| Function g | builtin-class NoneType | +| Function g | builtin-class float | +| Function g | builtin-class int | +| Function gen | builtin-class generator | +| Function h | builtin-class NoneType | +| Function h | builtin-class float | +| Function h | builtin-class int | +| Function not_none | builtin-class bool | diff --git a/python/ql/test/library-tests/PointsTo/returns/Test.ql b/python/ql/test/library-tests/PointsTo/returns/Test.ql new file mode 100644 index 000000000000..a30d0ef1c76c --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/returns/Test.ql @@ -0,0 +1,4 @@ +import python + +from PyFunctionObject f +select f.toString(), f.getAnInferredReturnType().toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/returns/options b/python/ql/test/library-tests/PointsTo/returns/options new file mode 100644 index 000000000000..58ad829f5a88 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/returns/options @@ -0,0 +1 @@ +optimize: true diff --git a/python/ql/test/library-tests/PointsTo/returns/test.py b/python/ql/test/library-tests/PointsTo/returns/test.py new file mode 100644 index 000000000000..af38b8064a34 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/returns/test.py @@ -0,0 +1,35 @@ + + + +def f(x): + if x: + return 1 + else: + return None + +def g(x, y): + if x: + return f(y) + else: + return 0.7 + +def h(a, b, c, d): + t = f(a) + v = g(b, c) + if d: + return t + else: + return v + +h(1,2,3,4) + +def not_none(a, b): + if a: + return True + elif b: + return False + #No fall through + raise Exception() + +def gen(): + yield 0 diff --git a/python/ql/test/library-tests/PointsTo/super/SuperMethodCall.expected b/python/ql/test/library-tests/PointsTo/super/SuperMethodCall.expected new file mode 100644 index 000000000000..69843b15d9d9 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/super/SuperMethodCall.expected @@ -0,0 +1,9 @@ +| 4 | ControlFlowNode for Attribute() | object.__init__ | +| 12 | ControlFlowNode for Attribute() | Base2.__init__ | +| 22 | ControlFlowNode for Attribute() | Base1.meth | +| 27 | ControlFlowNode for Attribute() | Derived1.meth | +| 32 | ControlFlowNode for Attribute() | Derived1.meth | +| 38 | ControlFlowNode for Attribute() | Derived2.meth | +| 52 | ControlFlowNode for Attribute() | DA.__init__ | +| 59 | ControlFlowNode for Attribute() | DA.__init__ | +| 66 | ControlFlowNode for Attribute() | DA.__init__ | diff --git a/python/ql/test/library-tests/PointsTo/super/SuperMethodCall.ql b/python/ql/test/library-tests/PointsTo/super/SuperMethodCall.ql new file mode 100644 index 000000000000..4df31ff0478b --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/super/SuperMethodCall.ql @@ -0,0 +1,9 @@ + +import python +import semmle.python.pointsto.PointsTo +import semmle.python.pointsto.PointsToContext + +from CallNode call, FunctionObject method +where PointsTo::Test::super_method_call(_, call, _, method) +select call.getLocation().getStartLine(), call.toString(), method.getQualifiedName() + diff --git a/python/ql/test/library-tests/PointsTo/super/test.py b/python/ql/test/library-tests/PointsTo/super/test.py new file mode 100644 index 000000000000..a5e8411b28db --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/super/test.py @@ -0,0 +1,66 @@ +class Base2(object): + + def __init__(self): + super(Base2, self).__init__() + + + +class Derived4(Base2): + + def __init__(self): + super(Base2, self) + return super(Derived4, self).__init__() + +class Base1(object): + + def meth(self): + pass + +class Derived1(Base1): + + def meth(self): + return super(Derived1, self).meth() + +class Derived2(Derived1): + + def meth(self): + return super(Derived2, self).meth() + +class Derived5(Derived1): + + def meth(self): + return super(Derived5, self).meth() + +#Incorrect use of super() +class Wrong1(Derived5, Derived2): + + def meth(self): + return super(Derived5, self).meth() + +#ODASA-5799 +class DA(object): + + def __init__(self): + do_something() + +class DB(DA): + + class DC(DA): + + def __init__(self): + sup = super(DB.DC, self) + sup.__init__() + +#Simpler variants +class DD(DA): + + def __init__(self): + sup = super(DD, self) + sup.__init__() + +class DE(DA): + + class DF(DA): + + def __init__(self): + super(DE.DF, self).__init__() diff --git a/python/ql/test/library-tests/PointsTo/version/VersionGuard.expected b/python/ql/test/library-tests/PointsTo/version/VersionGuard.expected new file mode 100644 index 000000000000..0a6b31a762d3 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/version/VersionGuard.expected @@ -0,0 +1,3 @@ +| 25 | BasicBlock | 2 | +| 28 | BasicBlock | 3 | +| 41 | BasicBlock | 2 | diff --git a/python/ql/test/library-tests/PointsTo/version/VersionGuard.ql b/python/ql/test/library-tests/PointsTo/version/VersionGuard.ql new file mode 100644 index 000000000000..03bfb33a3f33 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/version/VersionGuard.ql @@ -0,0 +1,9 @@ + +import python +import semmle.python.types.Version + +from VersionGuard vg, Location l, int v +where l = vg.getLastNode().getLocation() and +l.getFile().getName().matches("%test.py") +and (if vg.isTrue() then v = major_version() else v = 5-major_version()) +select l.getStartLine(), vg.toString(), v \ No newline at end of file diff --git a/python/ql/test/library-tests/PointsTo/version/VersionTest.expected b/python/ql/test/library-tests/PointsTo/version/VersionTest.expected new file mode 100644 index 000000000000..fd0c9160574d --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/version/VersionTest.expected @@ -0,0 +1,15 @@ +| 15 | ControlFlowNode for Compare | 2 | +| 22 | ControlFlowNode for Compare | 2 | +| 23 | ControlFlowNode for Compare | 3 | +| 51 | ControlFlowNode for Compare | 2 | +| 52 | ControlFlowNode for Compare | 3 | +| 54 | ControlFlowNode for Compare | 2 | +| 55 | ControlFlowNode for Compare | 3 | +| 57 | ControlFlowNode for Compare | 2 | +| 58 | ControlFlowNode for Compare | 3 | +| 59 | ControlFlowNode for Compare | 3 | +| 60 | ControlFlowNode for Compare | 2 | +| 61 | ControlFlowNode for Compare | 3 | +| 62 | ControlFlowNode for Compare | 2 | +| 65 | ControlFlowNode for Compare | 2 | +| 66 | ControlFlowNode for Compare | 3 | diff --git a/python/ql/test/library-tests/PointsTo/version/VersionTest.ql b/python/ql/test/library-tests/PointsTo/version/VersionTest.ql new file mode 100644 index 000000000000..0e6ca7fdee29 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/version/VersionTest.ql @@ -0,0 +1,9 @@ + +import python +import semmle.python.types.Version + +from VersionTest vt, Location l, int v +where l = vt.getNode().getLocation() and +l.getFile().getName().matches("%test.py") +and (if vt.isTrue() then v = major_version() else v = 5-major_version()) +select l.getStartLine(), vt.(ControlFlowNode).toString(), v diff --git a/python/ql/test/library-tests/PointsTo/version/module.py b/python/ql/test/library-tests/PointsTo/version/module.py new file mode 100644 index 000000000000..9e813a384287 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/version/module.py @@ -0,0 +1,5 @@ + +import sys + +os_test = sys.platform == "linux" +version_test = sys.version_info < (3,) diff --git a/python/ql/test/library-tests/PointsTo/version/test.py b/python/ql/test/library-tests/PointsTo/version/test.py new file mode 100644 index 000000000000..ad82c5ec425f --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/version/test.py @@ -0,0 +1,66 @@ +import sys + + + + + + + + + + + + +os_test = sys.platform == "linux" +version_test = sys.version_info < (3,) + +from module import os_test as t2 +from module import version_test as t3 + + +# Tests from six +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +if PY2: + version = 2 + +if PY3: + version = 3 + +if version == 2: + print("Version 2") + +if t2: + class G: pass +else: + def G(): pass + +g = G + +if t3: + class H: pass +else: + def H(): pass + +h = H + +#Some other forms of check. + +#Hexversion check (unlikely but a valid test) +PY2a = sys.hexversion < 0x03000000 +PY3a = sys.hexversion >= 0x03000000 + +PY2b = sys.hexversion < 0x03000000 +PY3b = sys.hexversion >= 0x03000000 + +PY2c = sys.version_info < (3,) +PY3c = sys.version_info >= (3,) +Py3d = sys.version_info >= (3,4) # Specific version of Python 3, rules out Python 2 +Py2d = sys.version_info < (2,7) +Py3e = sys.version_info[:2] >= (3,3) +Py2f = sys.version_info[:2] < (2,7) + +#From problem_report +Py2g = sys.version[0] < '3' +Py3h = sys.version[0] >= '3' diff --git a/python/ql/test/library-tests/attributes/SelfAttribute.expected b/python/ql/test/library-tests/attributes/SelfAttribute.expected new file mode 100644 index 000000000000..7d5843ffbe02 --- /dev/null +++ b/python/ql/test/library-tests/attributes/SelfAttribute.expected @@ -0,0 +1,4 @@ +| 10 | a1 | defined | +| 18 | a2 | defined | +| 21 | a0 | | +| 25 | a1 | guarded | \ No newline at end of file diff --git a/python/ql/test/library-tests/attributes/SelfAttribute.ql b/python/ql/test/library-tests/attributes/SelfAttribute.ql new file mode 100644 index 000000000000..0ccfe5a397c2 --- /dev/null +++ b/python/ql/test/library-tests/attributes/SelfAttribute.ql @@ -0,0 +1,11 @@ + +import python +import semmle.python.SelfAttribute + +from SelfAttributeRead sa, int line, string g, string l +where +line = sa.getLocation().getStartLine() and +if sa.guardedByHasattr() then g = "guarded" else g = "" and + +if sa.locallyDefined() then l = "defined" else l = "" +select line, sa.getName(), g + l diff --git a/python/ql/test/library-tests/attributes/test.py b/python/ql/test/library-tests/attributes/test.py new file mode 100644 index 000000000000..bf29f345d8fd --- /dev/null +++ b/python/ql/test/library-tests/attributes/test.py @@ -0,0 +1,25 @@ + + +class C(object): + + def __init__(self, x): + self.a0 = x + + def m1(self, y): + self.a1 = y + return self.a1 + + def m2(self, z): + self.a2 = z + if cond: + pass + else: + raise Error() + return self.a2 + + def m3(self): + return self.a0 + + def m4(self): + if hasattr(self, 'a1'): + return self.a1 \ No newline at end of file diff --git a/python/ql/test/library-tests/classes/abstract/Abstract.expected b/python/ql/test/library-tests/classes/abstract/Abstract.expected new file mode 100644 index 000000000000..1a9ca319692d --- /dev/null +++ b/python/ql/test/library-tests/classes/abstract/Abstract.expected @@ -0,0 +1,6 @@ +| class A | yes | +| class B | yes | +| class C | yes | +| class D | no | +| class E | yes | +| class F | no | diff --git a/python/ql/test/library-tests/classes/abstract/Abstract.ql b/python/ql/test/library-tests/classes/abstract/Abstract.ql new file mode 100644 index 000000000000..1117bc957906 --- /dev/null +++ b/python/ql/test/library-tests/classes/abstract/Abstract.ql @@ -0,0 +1,12 @@ + +import python + +from ClassObject cls, string abstract +where +not cls.isBuiltin() and +if cls.isAbstract() then + abstract = "yes" +else + abstract = "no" + +select cls.toString(), abstract diff --git a/python/ql/test/library-tests/classes/abstract/test.py b/python/ql/test/library-tests/classes/abstract/test.py new file mode 100644 index 000000000000..a8f5e803a922 --- /dev/null +++ b/python/ql/test/library-tests/classes/abstract/test.py @@ -0,0 +1,32 @@ + + +class A(object): + + def __init__(self): + raise NotImplementedError + + def _meth(self): + raise NotImplementedError + +class B(A): + + def _meth(self): + "Still abstract" + +class C(A): + pass + +class D(B): + + def __init__(self): + "Not abstract" + +class E(A): + + def __init__(self): + "Still abstract" + +class F(E): + + def _meth(self): + "Not abstract" diff --git a/python/ql/test/library-tests/classes/attr/class_attr.expected b/python/ql/test/library-tests/classes/attr/class_attr.expected new file mode 100644 index 000000000000..65d7b79023be --- /dev/null +++ b/python/ql/test/library-tests/classes/attr/class_attr.expected @@ -0,0 +1,32 @@ +| 5 | class OldStyle | a1 | int 1 | +| 5 | class OldStyle | a2 | List | +| 5 | class OldStyle | l | List | +| 5 | class OldStyle | meth1 | Function meth1 | +| 15 | class OldStyleDerived | a1 | int 1 | +| 15 | class OldStyleDerived | a2 | List | +| 15 | class OldStyleDerived | l | List | +| 15 | class OldStyleDerived | meth1 | Function meth1 | +| 15 | class OldStyleDerived | meth2 | Function meth2 | +| 21 | class NewStyle | a1 | int 1 | +| 21 | class NewStyle | a2 | List | +| 21 | class NewStyle | l | List | +| 21 | class NewStyle | meth3 | Function meth3 | +| 31 | class NewStyleDerived | a1 | int 1 | +| 31 | class NewStyleDerived | a2 | List | +| 31 | class NewStyleDerived | l | List | +| 31 | class NewStyleDerived | meth3 | Function meth3 | +| 31 | class NewStyleDerived | meth4 | Function meth4 | +| 41 | class Meta | meth5 | Function meth5 | +| 41 | class Meta | mro | Builtin-method mro | +| 50 | class WithMeta | a1 | int 1 | +| 50 | class WithMeta | a2 | List | +| 50 | class WithMeta | l | List | +| 50 | class WithMeta | meth6 | Function meth6 | +| 96 | class Oddities | float | builtin-class float | +| 96 | class Oddities | h | Builtin-function hash | +| 96 | class Oddities | int | builtin-class int | +| 96 | class Oddities | l | Builtin-function len | +| 103 | class Sub | float | builtin-class float | +| 103 | class Sub | h | Builtin-function hash | +| 103 | class Sub | int | builtin-class int | +| 103 | class Sub | l | Builtin-function len | \ No newline at end of file diff --git a/python/ql/test/library-tests/classes/attr/class_attr.ql b/python/ql/test/library-tests/classes/attr/class_attr.ql new file mode 100644 index 000000000000..0b283debd5db --- /dev/null +++ b/python/ql/test/library-tests/classes/attr/class_attr.ql @@ -0,0 +1,13 @@ +/** + * @name class_attr + * @kind test + * @problem.severity warning + */ + +import python + +from ClassObject cls, int line, string name, Object obj +where cls.hasLocationInfo(_, line, _, _, _) +and obj = cls.lookupAttribute(name) and +not cls.isC() and not name.matches("\\_\\_%\\_\\_") +select line, cls.toString(), name, obj.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/classes/attr/class_defined_attr.expected b/python/ql/test/library-tests/classes/attr/class_defined_attr.expected new file mode 100644 index 000000000000..26712c5f2751 --- /dev/null +++ b/python/ql/test/library-tests/classes/attr/class_defined_attr.expected @@ -0,0 +1,19 @@ +| 5 | class OldStyle | a1 | int 1 | +| 5 | class OldStyle | a2 | List | +| 5 | class OldStyle | l | List | +| 5 | class OldStyle | meth1 | Function meth1 | +| 15 | class OldStyleDerived | meth2 | Function meth2 | +| 21 | class NewStyle | a1 | int 1 | +| 21 | class NewStyle | a2 | List | +| 21 | class NewStyle | l | List | +| 21 | class NewStyle | meth3 | Function meth3 | +| 31 | class NewStyleDerived | meth4 | Function meth4 | +| 41 | class Meta | meth5 | Function meth5 | +| 50 | class WithMeta | a1 | int 1 | +| 50 | class WithMeta | a2 | List | +| 50 | class WithMeta | l | List | +| 50 | class WithMeta | meth6 | Function meth6 | +| 96 | class Oddities | float | builtin-class float | +| 96 | class Oddities | h | Builtin-function hash | +| 96 | class Oddities | int | builtin-class int | +| 96 | class Oddities | l | Builtin-function len | \ No newline at end of file diff --git a/python/ql/test/library-tests/classes/attr/class_defined_attr.ql b/python/ql/test/library-tests/classes/attr/class_defined_attr.ql new file mode 100644 index 000000000000..843b1ed2b3a8 --- /dev/null +++ b/python/ql/test/library-tests/classes/attr/class_defined_attr.ql @@ -0,0 +1,13 @@ +/** + * @name class_attr + * @kind test + * @problem.severity warning + */ + +import python + +from ClassObject cls, int line, string name, Object obj +where cls.hasLocationInfo(_, line, _, _, _) +and obj = cls.declaredAttribute(name) and +not cls.isC() and not name.matches("\\_\\_%\\_\\_") +select line, cls.toString(), name, obj.toString() diff --git a/python/ql/test/library-tests/classes/attr/class_defines_attr.expected b/python/ql/test/library-tests/classes/attr/class_defines_attr.expected new file mode 100644 index 000000000000..88adc304adae --- /dev/null +++ b/python/ql/test/library-tests/classes/attr/class_defines_attr.expected @@ -0,0 +1,22 @@ +| 5 | class OldStyle | a1 | +| 5 | class OldStyle | a2 | +| 5 | class OldStyle | a3 | +| 5 | class OldStyle | l | +| 5 | class OldStyle | meth1 | +| 15 | class OldStyleDerived | meth2 | +| 21 | class NewStyle | a1 | +| 21 | class NewStyle | a2 | +| 21 | class NewStyle | a3 | +| 21 | class NewStyle | l | +| 21 | class NewStyle | meth3 | +| 31 | class NewStyleDerived | meth4 | +| 41 | class Meta | meth5 | +| 50 | class WithMeta | a1 | +| 50 | class WithMeta | a2 | +| 50 | class WithMeta | a3 | +| 50 | class WithMeta | l | +| 50 | class WithMeta | meth6 | +| 96 | class Oddities | float | +| 96 | class Oddities | h | +| 96 | class Oddities | int | +| 96 | class Oddities | l | \ No newline at end of file diff --git a/python/ql/test/library-tests/classes/attr/class_defines_attr.ql b/python/ql/test/library-tests/classes/attr/class_defines_attr.ql new file mode 100644 index 000000000000..e9cfdee5ccd4 --- /dev/null +++ b/python/ql/test/library-tests/classes/attr/class_defines_attr.ql @@ -0,0 +1,13 @@ +/** + * @name class_attr + * @kind test + * @problem.severity warning + */ + +import python + +from ClassObject cls, int line, string name +where cls.hasLocationInfo(_, line, _, _, _) +and cls.declaresAttribute(name) and +not cls.isC() and not name.matches("\\_\\_%\\_\\_") +select line, cls.toString(), name diff --git a/python/ql/test/library-tests/classes/attr/class_has_attr.expected b/python/ql/test/library-tests/classes/attr/class_has_attr.expected new file mode 100644 index 000000000000..e73ad4d1894a --- /dev/null +++ b/python/ql/test/library-tests/classes/attr/class_has_attr.expected @@ -0,0 +1,37 @@ +| 5 | class OldStyle | a1 | +| 5 | class OldStyle | a2 | +| 5 | class OldStyle | a3 | +| 5 | class OldStyle | l | +| 5 | class OldStyle | meth1 | +| 15 | class OldStyleDerived | a1 | +| 15 | class OldStyleDerived | a2 | +| 15 | class OldStyleDerived | a3 | +| 15 | class OldStyleDerived | l | +| 15 | class OldStyleDerived | meth1 | +| 15 | class OldStyleDerived | meth2 | +| 21 | class NewStyle | a1 | +| 21 | class NewStyle | a2 | +| 21 | class NewStyle | a3 | +| 21 | class NewStyle | l | +| 21 | class NewStyle | meth3 | +| 31 | class NewStyleDerived | a1 | +| 31 | class NewStyleDerived | a2 | +| 31 | class NewStyleDerived | a3 | +| 31 | class NewStyleDerived | l | +| 31 | class NewStyleDerived | meth3 | +| 31 | class NewStyleDerived | meth4 | +| 41 | class Meta | meth5 | +| 41 | class Meta | mro | +| 50 | class WithMeta | a1 | +| 50 | class WithMeta | a2 | +| 50 | class WithMeta | a3 | +| 50 | class WithMeta | l | +| 50 | class WithMeta | meth6 | +| 96 | class Oddities | float | +| 96 | class Oddities | h | +| 96 | class Oddities | int | +| 96 | class Oddities | l | +| 103 | class Sub | float | +| 103 | class Sub | h | +| 103 | class Sub | int | +| 103 | class Sub | l | \ No newline at end of file diff --git a/python/ql/test/library-tests/classes/attr/class_has_attr.ql b/python/ql/test/library-tests/classes/attr/class_has_attr.ql new file mode 100644 index 000000000000..a274a1dd95b1 --- /dev/null +++ b/python/ql/test/library-tests/classes/attr/class_has_attr.ql @@ -0,0 +1,13 @@ +/** + * @name class_attr + * @kind test + * @problem.severity warning + */ + +import python + +from ClassObject cls, int line, string name +where cls.hasLocationInfo(_, line, _, _, _) +and cls.hasAttribute(name) and +not cls.isC() and not name.matches("\\_\\_%\\_\\_") +select line, cls.toString(), name diff --git a/python/ql/test/library-tests/classes/attr/hash.expected b/python/ql/test/library-tests/classes/attr/hash.expected new file mode 100644 index 000000000000..a65142422a0f --- /dev/null +++ b/python/ql/test/library-tests/classes/attr/hash.expected @@ -0,0 +1,2 @@ +| 92 | class Unhashable | NoneType None | +| 103 | class Sub | NoneType None | \ No newline at end of file diff --git a/python/ql/test/library-tests/classes/attr/hash.ql b/python/ql/test/library-tests/classes/attr/hash.ql new file mode 100644 index 000000000000..b4485634ccee --- /dev/null +++ b/python/ql/test/library-tests/classes/attr/hash.ql @@ -0,0 +1,15 @@ +/** + * @name class_attr + * @kind test + * @problem.severity warning + */ + +import python + +from ClassObject cls, int line, Object obj +where cls.hasLocationInfo(_, line, _, _, _) +and obj = cls.lookupAttribute("__hash__") and +not cls.isC() and +not obj = theObjectType().lookupAttribute("__hash__") and +not obj = theTypeType().lookupAttribute("__hash__") +select line, cls.toString(), obj.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/classes/attr/test.py b/python/ql/test/library-tests/classes/attr/test.py new file mode 100644 index 000000000000..37780f02d886 --- /dev/null +++ b/python/ql/test/library-tests/classes/attr/test.py @@ -0,0 +1,104 @@ +from undefined import unknown +k = 1 +l = [] + +class OldStyle: + + def meth1(self): + pass + + a1 = k + a2 = l + a3 = unknown + l = l + +class OldStyleDerived(OldStyle): + + def meth2(self): + pass + + +class NewStyle(object): + + def meth3(self): + pass + + a1 = k + a2 = l + a3 = unknown + l = l + +class NewStyleDerived(NewStyle): + + def meth4(self): + pass + + + + + + +class Meta(type): + + def __init__(cls, name, bases, dct): + type.__init__(cls, name, bases, dct) + cls.defined_in_meta = 1 + + def meth5(self): + pass + +class WithMeta(object): + + def meth6(self): + pass + + a1 = k + a2 = l + a3 = unknown + l = l + +#MRO tests + +#Inconsistent MRO + +class X(object): + pass + +class Y(X): + pass + +#Inconsistent MRO +class Z(X, Y): + pass + +#Ok +class W(Y, x): + pass + +class O: + pass + +#This is OK +class N(object, O): + pass + +# +# Assign builtin objects to class attributes + +len = len + +ord = 10 + +class Unhashable(object): + + __hash__ = None + +class Oddities(object): + + int = int + float = float + l = len + h = hash + +class Sub(Oddities, Unhashable): + pass diff --git a/python/ql/test/library-tests/classes/builtin_classes/options b/python/ql/test/library-tests/classes/builtin_classes/options new file mode 100644 index 000000000000..9a4d1ee4e642 --- /dev/null +++ b/python/ql/test/library-tests/classes/builtin_classes/options @@ -0,0 +1,2 @@ +semmle-extractor-options: --max-import-depth=2 -j +optimize: true diff --git a/python/ql/test/library-tests/classes/builtin_classes/test.expected b/python/ql/test/library-tests/classes/builtin_classes/test.expected new file mode 100644 index 000000000000..4cfd98b96bd1 --- /dev/null +++ b/python/ql/test/library-tests/classes/builtin_classes/test.expected @@ -0,0 +1 @@ +| builtin-class _ctypes._Pointer | builtin-class _ctypes.PyCPointerType | \ No newline at end of file diff --git a/python/ql/test/library-tests/classes/builtin_classes/test.py b/python/ql/test/library-tests/classes/builtin_classes/test.py new file mode 100644 index 000000000000..705b596bde85 --- /dev/null +++ b/python/ql/test/library-tests/classes/builtin_classes/test.py @@ -0,0 +1 @@ +from ctypes import * \ No newline at end of file diff --git a/python/ql/test/library-tests/classes/builtin_classes/test.ql b/python/ql/test/library-tests/classes/builtin_classes/test.ql new file mode 100644 index 000000000000..19a5a23b954b --- /dev/null +++ b/python/ql/test/library-tests/classes/builtin_classes/test.ql @@ -0,0 +1,5 @@ +import python + +from ClassObject c +where c.getName() = "_ctypes._Pointer" +select c.toString(), c.getAnInferredType().toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/classes/mro/C3.expected b/python/ql/test/library-tests/classes/mro/C3.expected new file mode 100644 index 000000000000..306ebb807f0d --- /dev/null +++ b/python/ql/test/library-tests/classes/mro/C3.expected @@ -0,0 +1,13 @@ +| class A | [A, object] | +| class B | [B, object] | +| class C | [C, object] | +| class D | [D, object] | +| class E | [E, object] | +| class K1 | [K1, A, B, C, object] | +| class K2 | [K2, D, B, E, object] | +| class K3 | [K3, D, A, object] | +| class M | [M, K1, K2, K3, D, A, B, C, E, object] | +| class T1 | [T1, object] | +| class T2 | [T2, object] | +| class T3 | [T3, T2, object] | +| class Test | [Test, T3, T2, T1, object] | diff --git a/python/ql/test/library-tests/classes/mro/C3.ql b/python/ql/test/library-tests/classes/mro/C3.ql new file mode 100644 index 000000000000..caaa43d3d45a --- /dev/null +++ b/python/ql/test/library-tests/classes/mro/C3.ql @@ -0,0 +1,9 @@ + +import python +import semmle.python.pointsto.MRO + +from ClassObject cls +where not cls.isBuiltin() + +select cls.toString(), new_style_mro(cls) + diff --git a/python/ql/test/library-tests/classes/mro/test.py b/python/ql/test/library-tests/classes/mro/test.py new file mode 100644 index 000000000000..9e66ad55650f --- /dev/null +++ b/python/ql/test/library-tests/classes/mro/test.py @@ -0,0 +1,15 @@ + + +#Check that MRO follows C3. + + +class T1(object): pass + +class T2(object): pass + +class T3(T2): pass + +class Test(T3, T1): pass + +#>>> Test.mro() +# [Test, T3, T2, T1, object] diff --git a/python/ql/test/library-tests/classes/mro/wikipedia.py b/python/ql/test/library-tests/classes/mro/wikipedia.py new file mode 100644 index 000000000000..a606313bfdfa --- /dev/null +++ b/python/ql/test/library-tests/classes/mro/wikipedia.py @@ -0,0 +1,29 @@ +#Copyright Wikipedia + +#THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). +#THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER +# THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. +#BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. +#TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE +# IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. + +class A(object): pass + +class B(object): pass + +class C(object): pass + +class D(object): pass + +class E(object): pass + +class K1(A, B, C): pass + +class K2(D, B, E): pass + +class K3(D, A): pass + +class M(K1, K2, K3): pass + +#>>> M.mro() +# [M, K1, K2, K3, D, A, B, C, E, object] diff --git a/python/ql/test/library-tests/comments/blocks.expected b/python/ql/test/library-tests/comments/blocks.expected new file mode 100644 index 000000000000..d337745d4d48 --- /dev/null +++ b/python/ql/test/library-tests/comments/blocks.expected @@ -0,0 +1,4 @@ +| 15 | 16 | Commented out code | +| 21 | 72 | Commented out code | +| 78 | 85 | Commented out code | +| 94 | 97 | Commented out code | \ No newline at end of file diff --git a/python/ql/test/library-tests/comments/blocks.ql b/python/ql/test/library-tests/comments/blocks.ql new file mode 100644 index 000000000000..e5c5f3ec3fda --- /dev/null +++ b/python/ql/test/library-tests/comments/blocks.ql @@ -0,0 +1,13 @@ +/** + * @name commented_out_code + * @description Insert description here... + * @kind problem + * @problem.severity warning + */ + +import python +import Lexical.CommentedOutCode + +from CommentedOutCodeBlock c, int bl, int el +where c.hasLocationInfo(_, bl, _, el, _) +select bl, el, c.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/comments/blocks_not_example.expected b/python/ql/test/library-tests/comments/blocks_not_example.expected new file mode 100644 index 000000000000..7a0a7158601b --- /dev/null +++ b/python/ql/test/library-tests/comments/blocks_not_example.expected @@ -0,0 +1,3 @@ +| 15 | 16 | Commented out code | +| 21 | 72 | Commented out code | +| 78 | 85 | Commented out code | \ No newline at end of file diff --git a/python/ql/test/library-tests/comments/blocks_not_example.ql b/python/ql/test/library-tests/comments/blocks_not_example.ql new file mode 100644 index 000000000000..ccc8c0ba50b3 --- /dev/null +++ b/python/ql/test/library-tests/comments/blocks_not_example.ql @@ -0,0 +1,7 @@ + +import python +import Lexical.CommentedOutCode + +from CommentedOutCodeBlock c, int bl, int el +where c.hasLocationInfo(_, bl, _, el, _) and not c.maybeExampleCode() +select bl, el, c.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/comments/length.expected b/python/ql/test/library-tests/comments/length.expected new file mode 100644 index 000000000000..b9a4b9bccae4 --- /dev/null +++ b/python/ql/test/library-tests/comments/length.expected @@ -0,0 +1,3 @@ +| 15 | 54 | true | +| 78 | 8 | true | +| 90 | 9 | false | diff --git a/python/ql/test/library-tests/comments/length.ql b/python/ql/test/library-tests/comments/length.ql new file mode 100644 index 000000000000..53d514e6b33a --- /dev/null +++ b/python/ql/test/library-tests/comments/length.ql @@ -0,0 +1,8 @@ + +import python +import Lexical.CommentedOutCode + +from CommentBlock block, int line, boolean code +where block.hasLocationInfo(_, line, _, _, _) and +if block instanceof CommentedOutCodeBlock then code = true else code = false +select line, block.length(), code diff --git a/python/ql/test/library-tests/comments/lines.expected b/python/ql/test/library-tests/comments/lines.expected new file mode 100644 index 000000000000..7c45cf8cd110 --- /dev/null +++ b/python/ql/test/library-tests/comments/lines.expected @@ -0,0 +1,46 @@ +| 15 | Comment #else: | +| 16 | Comment # do_something_else() | +| 21 | Comment #class CommentedOut: | +| 23 | Comment # def __init__(self): | +| 25 | Comment # pass | +| 27 | Comment # def method(self): | +| 29 | Comment # pass | +| 31 | Comment #def g(y): | +| 32 | Comment # assert y | +| 33 | Comment # with y: | +| 34 | Comment # # Commented out comment | +| 35 | Comment # if y: | +| 36 | Comment # do_something() | +| 37 | Comment # else: | +| 38 | Comment # do_something_else() | +| 40 | Comment #def h(z): | +| 41 | Comment # '''Doc string | +| 42 | Comment # ''' | +| 43 | Comment # # Commented out comment | +| 45 | Comment # followed_by_space() | +| 48 | Comment # more_code() | +| 50 | Comment #def j(): | +| 51 | Comment # """ Doc string """ | +| 52 | Comment # pass | +| 54 | Comment #def k(): | +| 56 | Comment # """ Doc string """ | +| 57 | Comment # pass | +| 59 | Comment #def l(): | +| 61 | Comment # """ | +| 62 | Comment # Doc string | +| 63 | Comment # """ | +| 65 | Comment # pass | +| 71 | Comment #def m(): | +| 72 | Comment # pass | +| 78 | Comment #with x: | +| 79 | Comment # pass | +| 80 | Comment #try: | +| 81 | Comment # call() | +| 82 | Comment #except Exception: | +| 83 | Comment # pass | +| 84 | Comment #except: | +| 85 | Comment # pass | +| 94 | Comment # def f(): | +| 95 | Comment # call() | +| 96 | Comment # x.y = z | +| 97 | Comment # return x | \ No newline at end of file diff --git a/python/ql/test/library-tests/comments/lines.ql b/python/ql/test/library-tests/comments/lines.ql new file mode 100644 index 000000000000..a07d2ac19533 --- /dev/null +++ b/python/ql/test/library-tests/comments/lines.ql @@ -0,0 +1,7 @@ + +import python +import Lexical.CommentedOutCode + +from CommentedOutCodeLine c, int l +where l = c.getLocation().getStartLine() +select l, c.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/comments/lines_not_example.expected b/python/ql/test/library-tests/comments/lines_not_example.expected new file mode 100644 index 000000000000..f476f967cda7 --- /dev/null +++ b/python/ql/test/library-tests/comments/lines_not_example.expected @@ -0,0 +1,42 @@ +| 15 | Comment #else: | +| 16 | Comment # do_something_else() | +| 21 | Comment #class CommentedOut: | +| 23 | Comment # def __init__(self): | +| 25 | Comment # pass | +| 27 | Comment # def method(self): | +| 29 | Comment # pass | +| 31 | Comment #def g(y): | +| 32 | Comment # assert y | +| 33 | Comment # with y: | +| 34 | Comment # # Commented out comment | +| 35 | Comment # if y: | +| 36 | Comment # do_something() | +| 37 | Comment # else: | +| 38 | Comment # do_something_else() | +| 40 | Comment #def h(z): | +| 41 | Comment # '''Doc string | +| 42 | Comment # ''' | +| 43 | Comment # # Commented out comment | +| 45 | Comment # followed_by_space() | +| 48 | Comment # more_code() | +| 50 | Comment #def j(): | +| 51 | Comment # """ Doc string """ | +| 52 | Comment # pass | +| 54 | Comment #def k(): | +| 56 | Comment # """ Doc string """ | +| 57 | Comment # pass | +| 59 | Comment #def l(): | +| 61 | Comment # """ | +| 62 | Comment # Doc string | +| 63 | Comment # """ | +| 65 | Comment # pass | +| 71 | Comment #def m(): | +| 72 | Comment # pass | +| 78 | Comment #with x: | +| 79 | Comment # pass | +| 80 | Comment #try: | +| 81 | Comment # call() | +| 82 | Comment #except Exception: | +| 83 | Comment # pass | +| 84 | Comment #except: | +| 85 | Comment # pass | \ No newline at end of file diff --git a/python/ql/test/library-tests/comments/lines_not_example.ql b/python/ql/test/library-tests/comments/lines_not_example.ql new file mode 100644 index 000000000000..e6fcaab9d936 --- /dev/null +++ b/python/ql/test/library-tests/comments/lines_not_example.ql @@ -0,0 +1,7 @@ + +import python +import Lexical.CommentedOutCode + +from CommentedOutCodeLine c, int l +where l = c.getLocation().getStartLine() and not c.maybeExampleCode() +select l, c.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/comments/test.py b/python/ql/test/library-tests/comments/test.py new file mode 100644 index 000000000000..162e6af3ff6e --- /dev/null +++ b/python/ql/test/library-tests/comments/test.py @@ -0,0 +1,103 @@ + +def e(): + #A real comment + some_code() + x = y + some_more_code() + "Ignore single commented out lines as it is too difficult to tell whether they are code" + #class C(object): + a_bit_more_code() + return 1 + +def f(x): + if x: + do_something() + #else: + # do_something_else() + +# Some non-code comments. +# Space immediately after scope start and between functions. +# +#class CommentedOut: +# +# def __init__(self): + +# pass +# +# def method(self): +# +# pass +# +#def g(y): +# assert y +# with y: +# # Commented out comment +# if y: +# do_something() +# else: +# do_something_else() +# +#def h(z): +# '''Doc string +# ''' +# # Commented out comment +# +# followed_by_space() + +# +# more_code() + +#def j(): +# """ Doc string """ +# pass + +#def k(): +# +# """ Doc string """ +# pass + +#def l(): +# +# """ +# Doc string +# """ +# +# pass + +# +# +# +# +#def m(): +# pass +# +# +# +some_code_to_break_up_comments() + +#with x: +# pass +#try: +# call() +#except Exception: +# pass +#except: +# pass + +def a_function_to_break_up_comments(): + pass + +# An example explaining +# something which contains +# the following code: +# +# def f(): +# call() +# x.y = z +# return x +# + + +def foo(): + # type: () -> None + pass \ No newline at end of file diff --git a/python/ql/test/library-tests/comments/type_hint.expected b/python/ql/test/library-tests/comments/type_hint.expected new file mode 100644 index 000000000000..1043c47cb630 --- /dev/null +++ b/python/ql/test/library-tests/comments/type_hint.expected @@ -0,0 +1 @@ +| test.py:102 | # type: () -> None | diff --git a/python/ql/test/library-tests/comments/type_hint.ql b/python/ql/test/library-tests/comments/type_hint.ql new file mode 100644 index 000000000000..55ec57c0d5b9 --- /dev/null +++ b/python/ql/test/library-tests/comments/type_hint.ql @@ -0,0 +1,6 @@ + +import python + +from TypeHintComment c +select c.getLocation().toString(), c.getText() + diff --git a/python/ql/test/library-tests/comparisons/Compare.expected b/python/ql/test/library-tests/comparisons/Compare.expected new file mode 100644 index 000000000000..70e08a0d3489 --- /dev/null +++ b/python/ql/test/library-tests/comparisons/Compare.expected @@ -0,0 +1,20 @@ +| 3 | ControlFlowNode for x == 4 | +| 5 | ControlFlowNode for x != 4 | +| 7 | ControlFlowNode for x > 4 | +| 9 | ControlFlowNode for x < 4 | +| 11 | ControlFlowNode for x >= 4 | +| 13 | ControlFlowNode for x <= 4 | +| 17 | ControlFlowNode for x < 0 | +| 17 | ControlFlowNode for z < 0 | +| 19 | ControlFlowNode for x >= 0 | +| 21 | ControlFlowNode for z >= 0 | +| 23 | ControlFlowNode for w >= 0 | +| 24 | ControlFlowNode for y < 7 | +| 26 | ControlFlowNode for y == 15 | +| 28 | ControlFlowNode for y > 10 | +| 30 | ControlFlowNode for y < 10 | +| 32 | ControlFlowNode for y < 12 | +| 34 | ControlFlowNode for y == 5 | +| 35 | ControlFlowNode for y != 5 | +| 36 | ControlFlowNode for z > 0 | +| 37 | ControlFlowNode for y < 3 | diff --git a/python/ql/test/library-tests/comparisons/Compare.ql b/python/ql/test/library-tests/comparisons/Compare.ql new file mode 100644 index 000000000000..84d97bbbbe26 --- /dev/null +++ b/python/ql/test/library-tests/comparisons/Compare.ql @@ -0,0 +1,9 @@ + +import python + +import semmle.python.Comparisons + +from Comparison c, ControlFlowNode l, CompareOp op, float k +where +c.tests(l, op, k) +select c.getLocation().getStartLine(), l + " " + op.repr() + " " + k diff --git a/python/ql/test/library-tests/comparisons/Compare2.expected b/python/ql/test/library-tests/comparisons/Compare2.expected new file mode 100644 index 000000000000..d574baf4d4e7 --- /dev/null +++ b/python/ql/test/library-tests/comparisons/Compare2.expected @@ -0,0 +1,28 @@ +| 40 | x == y+4 | +| 40 | y == x-4 | +| 42 | x != y+4 | +| 42 | y != x-4 | +| 44 | x > y+4 | +| 44 | y < x-4 | +| 46 | x < y+4 | +| 46 | y > x-4 | +| 48 | x >= y+4 | +| 48 | y <= x-4 | +| 50 | x <= y+4 | +| 50 | y >= x-4 | +| 54 | w < x+0 | +| 54 | x > w-0 | +| 55 | y < z+2 | +| 55 | z > y-2 | +| 57 | w >= x+0 | +| 57 | x <= w-0 | +| 59 | y < z+2 | +| 59 | z > y-2 | +| 78 | end < start+0 | +| 78 | start > end-0 | +| 80 | end == start-0 | +| 80 | start == end+0 | +| 87 | x > y+0 | +| 87 | y < x-0 | +| 94 | x > y+0 | +| 94 | y < x-0 | diff --git a/python/ql/test/library-tests/comparisons/Compare2.ql b/python/ql/test/library-tests/comparisons/Compare2.ql new file mode 100644 index 000000000000..70d954a4b0e8 --- /dev/null +++ b/python/ql/test/library-tests/comparisons/Compare2.ql @@ -0,0 +1,11 @@ + +import python + +import semmle.python.Comparisons + +from Comparison c, NameNode l, CompareOp op, NameNode r, float k, string add +where +c.tests(l, op, r, k) +and +(k < 0 and add = "" or k >= 0 and add = "+") +select c.getLocation().getStartLine(), l.getId() + " " + op.repr() + " " + r.getId() + add + k diff --git a/python/ql/test/library-tests/comparisons/CompareControls.expected b/python/ql/test/library-tests/comparisons/CompareControls.expected new file mode 100644 index 000000000000..c47e6d08e94e --- /dev/null +++ b/python/ql/test/library-tests/comparisons/CompareControls.expected @@ -0,0 +1,54 @@ +| 3 | x == 4 | 4 | +| 5 | x != 4 | 6 | +| 7 | x > 4 | 8 | +| 9 | x < 4 | 10 | +| 11 | x >= 4 | 12 | +| 13 | x <= 4 | 14 | +| 17 | x >= 0 | 16 | +| 17 | x >= 0 | 17 | +| 17 | x >= 0 | 19 | +| 17 | x >= 0 | 23 | +| 17 | x >= 0 | 24 | +| 17 | x >= 0 | 25 | +| 17 | x >= 0 | 28 | +| 17 | x >= 0 | 29 | +| 17 | x >= 0 | 30 | +| 17 | x >= 0 | 31 | +| 17 | x >= 0 | 33 | +| 17 | x >= 0 | 34 | +| 17 | x >= 0 | 36 | +| 17 | x >= 0 | 37 | +| 17 | z >= 0 | 16 | +| 17 | z >= 0 | 19 | +| 17 | z >= 0 | 23 | +| 17 | z >= 0 | 24 | +| 17 | z >= 0 | 25 | +| 17 | z >= 0 | 28 | +| 17 | z >= 0 | 29 | +| 17 | z >= 0 | 30 | +| 17 | z >= 0 | 31 | +| 17 | z >= 0 | 33 | +| 17 | z >= 0 | 34 | +| 17 | z >= 0 | 36 | +| 17 | z >= 0 | 37 | +| 23 | w < 0 | 16 | +| 23 | w < 0 | 30 | +| 23 | w < 0 | 31 | +| 23 | w < 0 | 33 | +| 23 | w < 0 | 34 | +| 23 | w < 0 | 36 | +| 23 | w < 0 | 37 | +| 23 | w >= 0 | 24 | +| 23 | w >= 0 | 25 | +| 23 | w >= 0 | 28 | +| 23 | w >= 0 | 29 | +| 24 | y < 7 | 25 | +| 24 | y >= 7 | 28 | +| 24 | y >= 7 | 29 | +| 28 | y > 10 | 29 | +| 30 | y < 10 | 31 | +| 30 | y < 10 | 33 | +| 32 | y < 12 | 33 | +| 34 | y == 5 | 36 | +| 34 | y == 5 | 37 | +| 36 | z > 0 | 37 | diff --git a/python/ql/test/library-tests/comparisons/CompareControls.ql b/python/ql/test/library-tests/comparisons/CompareControls.ql new file mode 100644 index 000000000000..01b35c0ffad9 --- /dev/null +++ b/python/ql/test/library-tests/comparisons/CompareControls.ql @@ -0,0 +1,10 @@ + +import python + +import semmle.python.Comparisons + +from ComparisonControlBlock comp, SsaVariable v, CompareOp op, float k, BasicBlock b +where +comp.controls(v.getAUse(), op, k, b) + +select comp.getTest().getLocation().getStartLine(), v.getId() + " " + op.repr() + " " + k, b.getNode(0).getLocation().getStartLine() diff --git a/python/ql/test/library-tests/comparisons/Implication.expected b/python/ql/test/library-tests/comparisons/Implication.expected new file mode 100644 index 000000000000..bb9abc1b012f --- /dev/null +++ b/python/ql/test/library-tests/comparisons/Implication.expected @@ -0,0 +1,154 @@ +| 3 | false | 3 | false | +| 3 | false | 5 | true | +| 3 | true | 3 | true | +| 3 | true | 5 | false | +| 5 | false | 3 | true | +| 5 | false | 5 | false | +| 5 | true | 3 | false | +| 5 | true | 5 | true | +| 7 | false | 7 | false | +| 7 | false | 13 | true | +| 7 | true | 3 | false | +| 7 | true | 5 | true | +| 7 | true | 7 | true | +| 7 | true | 9 | false | +| 7 | true | 11 | true | +| 7 | true | 13 | false | +| 9 | false | 9 | false | +| 9 | false | 11 | true | +| 9 | true | 3 | false | +| 9 | true | 5 | true | +| 9 | true | 7 | false | +| 9 | true | 9 | true | +| 9 | true | 11 | false | +| 9 | true | 13 | true | +| 11 | false | 3 | false | +| 11 | false | 5 | true | +| 11 | false | 7 | false | +| 11 | false | 9 | true | +| 11 | false | 11 | false | +| 11 | false | 13 | true | +| 11 | true | 9 | false | +| 11 | true | 11 | true | +| 13 | false | 3 | false | +| 13 | false | 5 | true | +| 13 | false | 7 | true | +| 13 | false | 9 | false | +| 13 | false | 11 | true | +| 13 | false | 13 | false | +| 13 | true | 7 | false | +| 13 | true | 13 | true | +| 17 | false | 17 | false | +| 17 | false | 19 | true | +| 17 | false | 21 | true | +| 17 | true | 17 | true | +| 17 | true | 19 | false | +| 17 | true | 21 | false | +| 19 | false | 17 | true | +| 19 | false | 19 | false | +| 19 | true | 17 | false | +| 19 | true | 19 | true | +| 21 | false | 17 | true | +| 21 | false | 21 | false | +| 21 | true | 17 | false | +| 21 | true | 21 | true | +| 23 | false | 23 | false | +| 23 | true | 23 | true | +| 24 | false | 24 | false | +| 24 | true | 24 | true | +| 24 | true | 26 | false | +| 24 | true | 28 | false | +| 24 | true | 30 | true | +| 26 | false | 26 | false | +| 26 | true | 26 | true | +| 28 | false | 26 | false | +| 28 | false | 28 | false | +| 28 | true | 24 | false | +| 28 | true | 28 | true | +| 28 | true | 30 | false | +| 30 | false | 24 | false | +| 30 | false | 30 | false | +| 30 | true | 26 | false | +| 30 | true | 28 | false | +| 30 | true | 30 | true | +| 32 | false | 32 | false | +| 32 | true | 32 | true | +| 34 | false | 34 | false | +| 34 | false | 35 | true | +| 34 | true | 34 | true | +| 34 | true | 35 | false | +| 35 | false | 34 | true | +| 35 | false | 35 | false | +| 35 | true | 34 | false | +| 35 | true | 35 | true | +| 36 | false | 36 | false | +| 36 | true | 36 | true | +| 37 | false | 37 | false | +| 37 | true | 34 | false | +| 37 | true | 35 | true | +| 37 | true | 37 | true | +| 40 | false | 40 | false | +| 40 | false | 42 | true | +| 40 | true | 40 | true | +| 40 | true | 42 | false | +| 42 | false | 40 | true | +| 42 | false | 42 | false | +| 42 | true | 40 | false | +| 42 | true | 42 | true | +| 44 | false | 44 | false | +| 44 | false | 50 | true | +| 44 | true | 40 | false | +| 44 | true | 42 | true | +| 44 | true | 44 | true | +| 44 | true | 46 | false | +| 44 | true | 48 | true | +| 44 | true | 50 | false | +| 46 | false | 46 | false | +| 46 | false | 48 | true | +| 46 | true | 40 | false | +| 46 | true | 42 | true | +| 46 | true | 44 | false | +| 46 | true | 46 | true | +| 46 | true | 48 | false | +| 46 | true | 50 | true | +| 48 | false | 40 | false | +| 48 | false | 42 | true | +| 48 | false | 44 | false | +| 48 | false | 46 | true | +| 48 | false | 48 | false | +| 48 | false | 50 | true | +| 48 | true | 46 | false | +| 48 | true | 48 | true | +| 50 | false | 40 | false | +| 50 | false | 42 | true | +| 50 | false | 44 | true | +| 50 | false | 46 | false | +| 50 | false | 48 | true | +| 50 | false | 50 | false | +| 50 | true | 44 | false | +| 50 | true | 50 | true | +| 54 | false | 54 | false | +| 54 | false | 57 | true | +| 54 | true | 54 | true | +| 54 | true | 57 | false | +| 55 | false | 55 | false | +| 55 | false | 59 | false | +| 55 | true | 55 | true | +| 55 | true | 59 | true | +| 57 | false | 54 | true | +| 57 | false | 57 | false | +| 57 | true | 54 | false | +| 57 | true | 57 | true | +| 59 | false | 55 | false | +| 59 | false | 59 | false | +| 59 | true | 55 | true | +| 59 | true | 59 | true | +| 78 | false | 78 | false | +| 78 | true | 78 | true | +| 78 | true | 80 | false | +| 80 | false | 80 | false | +| 80 | true | 80 | true | +| 87 | false | 87 | false | +| 87 | true | 87 | true | +| 94 | false | 94 | false | +| 94 | true | 94 | true | diff --git a/python/ql/test/library-tests/comparisons/Implication.ql b/python/ql/test/library-tests/comparisons/Implication.ql new file mode 100644 index 000000000000..f24d1d422349 --- /dev/null +++ b/python/ql/test/library-tests/comparisons/Implication.ql @@ -0,0 +1,9 @@ + +import python +import semmle.python.Comparisons + +from Comparison a, Comparison that, boolean thisIsTrue, boolean thatIsTrue + +where a.impliesThat(thisIsTrue, that, thatIsTrue) + +select a.getLocation().getStartLine(), thisIsTrue, that.getLocation().getStartLine(), thatIsTrue \ No newline at end of file diff --git a/python/ql/test/library-tests/comparisons/options b/python/ql/test/library-tests/comparisons/options new file mode 100644 index 000000000000..3e57ce3b2466 --- /dev/null +++ b/python/ql/test/library-tests/comparisons/options @@ -0,0 +1 @@ +semmle-extractor-options: --dont-split-graph diff --git a/python/ql/test/library-tests/comparisons/test.py b/python/ql/test/library-tests/comparisons/test.py new file mode 100644 index 000000000000..dc2ac175d0b1 --- /dev/null +++ b/python/ql/test/library-tests/comparisons/test.py @@ -0,0 +1,96 @@ + +def simple_tests(x): + if x == 4: + pass + if x != 4: + pass + if x > 4: + pass + if x < 4: + pass + if x >= 4: + pass + if x <= 4: + pass + +def f(w, x, y, z): + if x < 0 or z < 0: + raise Exception() + if x >= 0: # Useless test due to x < 0 being false + y += 1 + if z >= 0: # Useless test due to z < 0 being false + y += 1 + while w >= 0: + if y < 7: + z += 1 + if y == 15: # Useless test due to y < 10 being true + z += 1 + elif y > 10: + y -= 1 + if y < 10: + y += 1 + if y < 12: #A useless test, but too complex to infer. + pass + if (not + y != 5 and + z > 0): + w = 0 if y < 3 else 1 #Useless test as y is 5 + +def simple_tests2(x, y): + if x == y+4: + pass + if x != y+4: + pass + if x > y+4: + pass + if x < y+4: + pass + if x >= y+4: + pass + if x <= y+4: + pass + +def g(w, x, y, z): + if (w < x or + y < z+2): + raise Exception() + if w >= x: # Useless test due to w < x being false + pass + if z > y-2: # Useless test due to y < z+2 being false + y += 1 + +#Complex things we can't analyse +def h(a,b,c,d): + if a < b - g(c): + pass + if a(c) < b(d): + pass + if a < 10 + b + c: + pass + if a > 20 - g(c): + pass + if a + 10 > g(c): + pass + + +#ODASA-5643 +def validate_series(start, end): + if end < start: + raise error() + if start == end: + raise error() + return start, end + +def big1(x, y): + if x + 10000000000000000 > y + 10000000000000001: + return + if x > y: + # Redundant (but cannot be sure due to FP rounding errors) + pass + +def big2(x, y): + if x + 10000000000000000 > y + 10000000000000001: + return + if x > y: + # Not redundant (but might appear to be due to FP rounding errors) + pass diff --git a/python/ql/test/library-tests/comprehensions/AST.expected b/python/ql/test/library-tests/comprehensions/AST.expected new file mode 100644 index 000000000000..0ec20fcb99af --- /dev/null +++ b/python/ql/test/library-tests/comprehensions/AST.expected @@ -0,0 +1,50 @@ +| 2 | test.py:2:1:5:1 | .0 | 2 | test.py:2:1:5:1 | For | +| 2 | test.py:2:1:5:1 | .0 | 2 | test.py:2:1:5:1 | Function listcomp | +| 2 | test.py:2:1:5:1 | ExprStmt | 0 | test.py:0:0:0:0 | Module test | +| 2 | test.py:2:1:5:1 | For | 2 | test.py:2:1:5:1 | For | +| 2 | test.py:2:1:5:1 | For | 2 | test.py:2:1:5:1 | Function listcomp | +| 2 | test.py:2:1:5:1 | Function listcomp | 2 | test.py:2:1:5:1 | ListComp | +| 2 | test.py:2:1:5:1 | ListComp | 2 | test.py:2:1:5:1 | ExprStmt | +| 2 | test.py:2:5:2:5 | i | 2 | test.py:2:5:2:7 | BinaryExpr | +| 2 | test.py:2:5:2:7 | BinaryExpr | 2 | test.py:2:5:2:7 | Yield | +| 2 | test.py:2:5:2:7 | ExprStmt | 2 | test.py:2:1:5:1 | For | +| 2 | test.py:2:5:2:7 | Yield | 2 | test.py:2:5:2:7 | ExprStmt | +| 2 | test.py:2:7:2:7 | j | 2 | test.py:2:5:2:7 | BinaryExpr | +| 3 | test.py:3:9:3:9 | i | 2 | test.py:2:1:5:1 | For | +| 3 | test.py:3:14:3:18 | range | 3 | test.py:3:14:3:21 | range() | +| 3 | test.py:3:14:3:21 | range() | 2 | test.py:2:1:5:1 | ListComp | +| 3 | test.py:3:20:3:20 | IntegerLiteral | 3 | test.py:3:14:3:21 | range() | +| 4 | test.py:4:9:4:9 | j | 2 | test.py:2:1:5:1 | For | +| 4 | test.py:4:14:4:18 | range | 4 | test.py:4:14:4:21 | range() | +| 4 | test.py:4:14:4:21 | range() | 2 | test.py:2:1:5:1 | For | +| 4 | test.py:4:20:4:20 | IntegerLiteral | 4 | test.py:4:14:4:21 | range() | +| 7 | test.py:7:1:9:1 | .0 | 7 | test.py:7:1:9:1 | For | +| 7 | test.py:7:1:9:1 | .0 | 7 | test.py:7:1:9:1 | Function setcomp | +| 7 | test.py:7:1:9:1 | ExprStmt | 0 | test.py:0:0:0:0 | Module test | +| 7 | test.py:7:1:9:1 | For | 7 | test.py:7:1:9:1 | Function setcomp | +| 7 | test.py:7:1:9:1 | Function setcomp | 7 | test.py:7:1:9:1 | SetComp | +| 7 | test.py:7:1:9:1 | SetComp | 7 | test.py:7:1:9:1 | ExprStmt | +| 8 | test.py:8:5:8:5 | x | 8 | test.py:8:5:8:9 | BinaryExpr | +| 8 | test.py:8:5:8:9 | BinaryExpr | 8 | test.py:8:5:8:9 | Yield | +| 8 | test.py:8:5:8:9 | ExprStmt | 7 | test.py:7:1:9:1 | For | +| 8 | test.py:8:5:8:9 | Yield | 8 | test.py:8:5:8:9 | ExprStmt | +| 8 | test.py:8:9:8:9 | x | 8 | test.py:8:5:8:9 | BinaryExpr | +| 8 | test.py:8:15:8:15 | x | 7 | test.py:7:1:9:1 | For | +| 8 | test.py:8:20:8:22 | seq | 7 | test.py:7:1:9:1 | SetComp | +| 11 | test.py:11:1:15:1 | .0 | 11 | test.py:11:1:15:1 | For | +| 11 | test.py:11:1:15:1 | .0 | 11 | test.py:11:1:15:1 | Function dictcomp | +| 11 | test.py:11:1:15:1 | DictComp | 11 | test.py:11:1:15:1 | ExprStmt | +| 11 | test.py:11:1:15:1 | ExprStmt | 0 | test.py:0:0:0:0 | Module test | +| 11 | test.py:11:1:15:1 | For | 11 | test.py:11:1:15:1 | Function dictcomp | +| 11 | test.py:11:1:15:1 | Function dictcomp | 11 | test.py:11:1:15:1 | DictComp | +| 12 | test.py:12:5:12:5 | y | 12 | test.py:12:5:12:10 | Attribute | +| 12 | test.py:12:5:12:10 | Attribute | 12 | test.py:12:5:12:16 | Tuple | +| 12 | test.py:12:5:12:16 | ExprStmt | 11 | test.py:11:1:15:1 | For | +| 12 | test.py:12:5:12:16 | Tuple | 12 | test.py:12:5:12:16 | Yield | +| 12 | test.py:12:5:12:16 | Yield | 12 | test.py:12:5:12:16 | ExprStmt | +| 12 | test.py:12:14:12:14 | z | 12 | test.py:12:14:12:16 | z() | +| 12 | test.py:12:14:12:16 | z() | 12 | test.py:12:5:12:16 | Tuple | +| 13 | test.py:13:9:13:9 | y | 13 | test.py:13:9:13:12 | Tuple | +| 13 | test.py:13:9:13:12 | Tuple | 11 | test.py:11:1:15:1 | For | +| 13 | test.py:13:12:13:12 | z | 13 | test.py:13:9:13:12 | Tuple | +| 14 | test.py:14:5:14:11 | mapping | 11 | test.py:11:1:15:1 | DictComp | diff --git a/python/ql/test/library-tests/comprehensions/AST.ql b/python/ql/test/library-tests/comprehensions/AST.ql new file mode 100644 index 000000000000..a0063daf0a67 --- /dev/null +++ b/python/ql/test/library-tests/comprehensions/AST.ql @@ -0,0 +1,5 @@ +import python + +from AstNode child, AstNode parent +where child.getParentNode() = parent +select child.getLocation().getStartLine(), child, parent.getLocation().getStartLine(), parent diff --git a/python/ql/test/library-tests/comprehensions/Flow.expected b/python/ql/test/library-tests/comprehensions/Flow.expected new file mode 100644 index 000000000000..efcf64bfb9f5 --- /dev/null +++ b/python/ql/test/library-tests/comprehensions/Flow.expected @@ -0,0 +1,48 @@ +| 0 | Entry node for Module test | 3 | ControlFlowNode for range | +| 2 | ControlFlowNode for .0 | 2 | ControlFlowNode for .0 | +| 2 | ControlFlowNode for .0 | 2 | ControlFlowNode for For | +| 2 | ControlFlowNode for BinaryExpr | 2 | ControlFlowNode for Yield | +| 2 | ControlFlowNode for For | 2 | ControlFlowNode for For | +| 2 | ControlFlowNode for For | 2 | Exit node for Function listcomp | +| 2 | ControlFlowNode for For | 3 | ControlFlowNode for i | +| 2 | ControlFlowNode for For | 4 | ControlFlowNode for j | +| 2 | ControlFlowNode for ListComp | 8 | ControlFlowNode for seq | +| 2 | ControlFlowNode for Yield | 2 | ControlFlowNode for For | +| 2 | ControlFlowNode for i | 2 | ControlFlowNode for j | +| 2 | ControlFlowNode for j | 2 | ControlFlowNode for BinaryExpr | +| 2 | Entry node for Function listcomp | 2 | ControlFlowNode for .0 | +| 3 | ControlFlowNode for IntegerLiteral | 3 | ControlFlowNode for range() | +| 3 | ControlFlowNode for i | 4 | ControlFlowNode for range | +| 3 | ControlFlowNode for range | 3 | ControlFlowNode for IntegerLiteral | +| 3 | ControlFlowNode for range() | 2 | ControlFlowNode for ListComp | +| 4 | ControlFlowNode for IntegerLiteral | 4 | ControlFlowNode for range() | +| 4 | ControlFlowNode for j | 2 | ControlFlowNode for i | +| 4 | ControlFlowNode for range | 4 | ControlFlowNode for IntegerLiteral | +| 4 | ControlFlowNode for range() | 2 | ControlFlowNode for For | +| 7 | ControlFlowNode for .0 | 7 | ControlFlowNode for .0 | +| 7 | ControlFlowNode for .0 | 7 | ControlFlowNode for For | +| 7 | ControlFlowNode for For | 7 | Exit node for Function setcomp | +| 7 | ControlFlowNode for For | 8 | ControlFlowNode for x | +| 7 | ControlFlowNode for SetComp | 14 | ControlFlowNode for mapping | +| 7 | Entry node for Function setcomp | 7 | ControlFlowNode for .0 | +| 8 | ControlFlowNode for BinaryExpr | 8 | ControlFlowNode for Yield | +| 8 | ControlFlowNode for Yield | 7 | ControlFlowNode for For | +| 8 | ControlFlowNode for seq | 7 | ControlFlowNode for SetComp | +| 8 | ControlFlowNode for x | 8 | ControlFlowNode for BinaryExpr | +| 8 | ControlFlowNode for x | 8 | ControlFlowNode for x | +| 11 | ControlFlowNode for .0 | 11 | ControlFlowNode for .0 | +| 11 | ControlFlowNode for .0 | 11 | ControlFlowNode for For | +| 11 | ControlFlowNode for DictComp | 0 | Exit node for Module test | +| 11 | ControlFlowNode for For | 11 | Exit node for Function dictcomp | +| 11 | ControlFlowNode for For | 13 | ControlFlowNode for Tuple | +| 11 | Entry node for Function dictcomp | 11 | ControlFlowNode for .0 | +| 12 | ControlFlowNode for Attribute | 12 | ControlFlowNode for Tuple | +| 12 | ControlFlowNode for Tuple | 12 | ControlFlowNode for Yield | +| 12 | ControlFlowNode for Yield | 11 | ControlFlowNode for For | +| 12 | ControlFlowNode for y | 12 | ControlFlowNode for Attribute | +| 12 | ControlFlowNode for z | 12 | ControlFlowNode for z() | +| 12 | ControlFlowNode for z() | 12 | ControlFlowNode for y | +| 13 | ControlFlowNode for Tuple | 13 | ControlFlowNode for y | +| 13 | ControlFlowNode for y | 13 | ControlFlowNode for z | +| 13 | ControlFlowNode for z | 12 | ControlFlowNode for z | +| 14 | ControlFlowNode for mapping | 11 | ControlFlowNode for DictComp | diff --git a/python/ql/test/library-tests/comprehensions/Flow.ql b/python/ql/test/library-tests/comprehensions/Flow.ql new file mode 100644 index 000000000000..e19d4d75abe7 --- /dev/null +++ b/python/ql/test/library-tests/comprehensions/Flow.ql @@ -0,0 +1,5 @@ +import python + +from ControlFlowNode p, ControlFlowNode s +where p.getASuccessor() = s +select p.getLocation().getStartLine(), p.toString(), s.getLocation().getStartLine(), s.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/comprehensions/test.py b/python/ql/test/library-tests/comprehensions/test.py new file mode 100644 index 000000000000..b4504f8960da --- /dev/null +++ b/python/ql/test/library-tests/comprehensions/test.py @@ -0,0 +1,15 @@ + +[ i+j + for i in range(1) + for j in range(2) +] + +{ + x * x for x in seq +} + +{ + y.attr : z() + for y, z in + mapping +} diff --git a/python/ql/test/library-tests/dependencies/ArchitectDependencies.expected b/python/ql/test/library-tests/dependencies/ArchitectDependencies.expected new file mode 100644 index 000000000000..2bb7df4b7def --- /dev/null +++ b/python/ql/test/library-tests/dependencies/ArchitectDependencies.expected @@ -0,0 +1,10 @@ +| standard/python/attribute | Module a | Class B | Attribute | +| standard/python/attribute | Module b | Class C | Attribute | +| standard/python/import | Module a | Module b | Import | +| standard/python/import | Module b | Module c | Import | +| standard/python/import | Module c | Module b | Import | +| standard/python/inheritance | Class A | Class B | ClassExpr | +| standard/python/inheritance | Class A | Class C | ClassExpr | +| standard/python/inheritance | Class B | Class C | ClassExpr | +| standard/python/use | Module a | Class B | Attribute | +| standard/python/use | Module b | Class C | Attribute | diff --git a/python/ql/test/library-tests/dependencies/ArchitectDependencies.ql b/python/ql/test/library-tests/dependencies/ArchitectDependencies.ql new file mode 100644 index 000000000000..ce33d7c7acde --- /dev/null +++ b/python/ql/test/library-tests/dependencies/ArchitectDependencies.ql @@ -0,0 +1,9 @@ + +import python +import Architect.Common.DependencyCategory +import Architect.Architect + +from DependencyCategory dk, DependencyElement source, DependencyElement target, DependencyElement cause +where dk.isADependency(source, target, cause) +select dk.toString(), source.toString(), target.toString(), cause.toString() + diff --git a/python/ql/test/library-tests/dependencies/Categories.expected b/python/ql/test/library-tests/dependencies/Categories.expected new file mode 100644 index 000000000000..9cfbbe102a81 --- /dev/null +++ b/python/ql/test/library-tests/dependencies/Categories.expected @@ -0,0 +1,4 @@ +| standard/python/attribute | +| standard/python/import | +| standard/python/inheritance | +| standard/python/use | diff --git a/python/ql/test/library-tests/dependencies/Categories.ql b/python/ql/test/library-tests/dependencies/Categories.ql new file mode 100644 index 000000000000..6866ab072cad --- /dev/null +++ b/python/ql/test/library-tests/dependencies/Categories.ql @@ -0,0 +1,13 @@ +/** + * @name Categories + * @description Insert description here... + * @kind problem + * @problem.severity warning + */ + +import python +import Architect.Common.DependencyCategory +import Architect.Architect + +from DependencyCategory dk +select dk diff --git a/python/ql/test/library-tests/dependencies/Dependencies.expected b/python/ql/test/library-tests/dependencies/Dependencies.expected new file mode 100644 index 000000000000..90839fffe550 --- /dev/null +++ b/python/ql/test/library-tests/dependencies/Dependencies.expected @@ -0,0 +1,16 @@ +| attribute | a.py | 3 | Attribute | class B | +| attribute | b.py | 3 | Attribute | class C | +| import | a.py | 1 | Import | Module b | +| import | b.py | 1 | Import | Module c | +| import | c.py | 1 | Import | Module b | +| inheritance | a.py | 3 | ClassExpr | builtin-class object | +| inheritance | a.py | 3 | ClassExpr | builtin-class type | +| inheritance | a.py | 3 | ClassExpr | class B | +| inheritance | a.py | 3 | ClassExpr | class C | +| inheritance | b.py | 3 | ClassExpr | builtin-class object | +| inheritance | b.py | 3 | ClassExpr | builtin-class type | +| inheritance | b.py | 3 | ClassExpr | class C | +| inheritance | c.py | 3 | ClassExpr | builtin-class object | +| inheritance | c.py | 3 | ClassExpr | builtin-class type | +| use | a.py | 3 | Attribute | class B | +| use | b.py | 3 | Attribute | class C | diff --git a/python/ql/test/library-tests/dependencies/Dependencies.ql b/python/ql/test/library-tests/dependencies/Dependencies.ql new file mode 100644 index 000000000000..b5bedbe7b3c3 --- /dev/null +++ b/python/ql/test/library-tests/dependencies/Dependencies.ql @@ -0,0 +1,8 @@ + +import python +import semmle.python.dependencies.Dependencies + +from DependencyKind dk, AstNode src, Object target +where dk.isADependency(src, target) +select dk.toString(), src.getLocation().getFile().getShortName(), src.getLocation().getStartLine(), src.toString(), target.toString() + diff --git a/python/ql/test/library-tests/dependencies/a.py b/python/ql/test/library-tests/dependencies/a.py new file mode 100644 index 000000000000..b174bff58ca8 --- /dev/null +++ b/python/ql/test/library-tests/dependencies/a.py @@ -0,0 +1,4 @@ +import b + +class A(b.B): + pass diff --git a/python/ql/test/library-tests/dependencies/b.py b/python/ql/test/library-tests/dependencies/b.py new file mode 100644 index 000000000000..24586e4ddc6f --- /dev/null +++ b/python/ql/test/library-tests/dependencies/b.py @@ -0,0 +1,4 @@ +import c + +class B(c.C): + pass diff --git a/python/ql/test/library-tests/dependencies/c.py b/python/ql/test/library-tests/dependencies/c.py new file mode 100644 index 000000000000..2ba0664a5b5e --- /dev/null +++ b/python/ql/test/library-tests/dependencies/c.py @@ -0,0 +1,5 @@ +import b + +class C(object): + def foo(self): + b = B() diff --git a/python/ql/test/library-tests/descriptors/Descriptors.expected b/python/ql/test/library-tests/descriptors/Descriptors.expected new file mode 100644 index 000000000000..1c9d4436a945 --- /dev/null +++ b/python/ql/test/library-tests/descriptors/Descriptors.expected @@ -0,0 +1,10 @@ +| builtin-class classmethod | non-overriding | +| builtin-class classmethod_descriptor | non-overriding | +| builtin-class function | non-overriding | +| builtin-class getset_descriptor | overriding | +| builtin-class member_descriptor | overriding | +| builtin-class method_descriptor | non-overriding | +| builtin-class property | overriding | +| builtin-class staticmethod | non-overriding | +| builtin-class super | non-overriding | +| builtin-class wrapper_descriptor | non-overriding | diff --git a/python/ql/test/library-tests/descriptors/Descriptors.ql b/python/ql/test/library-tests/descriptors/Descriptors.ql new file mode 100644 index 000000000000..658091bfe4ef --- /dev/null +++ b/python/ql/test/library-tests/descriptors/Descriptors.ql @@ -0,0 +1,13 @@ + +import python + +from ClassObject cls, string kind +where cls.isDescriptorType() and +/* Exclude bound-method as its name differs between 2 and 3 */ +not cls = theBoundMethodType() and +(if cls.isOverridingDescriptorType() then + kind = "overriding" + else + kind = "non-overriding" +) +select cls.toString(), kind \ No newline at end of file diff --git a/python/ql/test/library-tests/descriptors/Methods.expected b/python/ql/test/library-tests/descriptors/Methods.expected new file mode 100644 index 000000000000..efd066e8b4c6 --- /dev/null +++ b/python/ql/test/library-tests/descriptors/Methods.expected @@ -0,0 +1,6 @@ +| 16 | classmethod() | 17 | Function c1 | +| 23 | classmethod() | 20 | Function c2 | +| 24 | classmethod() | 20 | Function c2 | +| 26 | staticmethod() | 27 | Function s1 | +| 33 | staticmethod() | 30 | Function s2 | +| 34 | staticmethod() | 30 | Function s2 | \ No newline at end of file diff --git a/python/ql/test/library-tests/descriptors/Methods.ql b/python/ql/test/library-tests/descriptors/Methods.ql new file mode 100644 index 000000000000..75d3092198db --- /dev/null +++ b/python/ql/test/library-tests/descriptors/Methods.ql @@ -0,0 +1,15 @@ + +import python +import semmle.python.types.Descriptors + +int lineof(Object o) { + result = o.getOrigin().getLocation().getStartLine() +} + +from Object m, FunctionObject f +where + m.(ClassMethodObject).getFunction() = f + or + m.(StaticMethodObject).getFunction() = f +select lineof(m), m.toString(), lineof(f), f.toString() + diff --git a/python/ql/test/library-tests/descriptors/Properties.expected b/python/ql/test/library-tests/descriptors/Properties.expected new file mode 100644 index 000000000000..3eb736d618b7 --- /dev/null +++ b/python/ql/test/library-tests/descriptors/Properties.expected @@ -0,0 +1 @@ +| 6 | Property f | 7 | Function f | 11 | Function f | diff --git a/python/ql/test/library-tests/descriptors/Properties.ql b/python/ql/test/library-tests/descriptors/Properties.ql new file mode 100644 index 000000000000..e27ca6beb3c3 --- /dev/null +++ b/python/ql/test/library-tests/descriptors/Properties.ql @@ -0,0 +1,13 @@ + +import python +import semmle.python.types.Descriptors + +int lineof(Object o) { + result = o.getOrigin().getLocation().getStartLine() +} + +from PropertyObject p, FunctionObject getter, FunctionObject setter +where +getter = p.getGetter() and setter = p.getSetter() +select lineof(p), p.toString(), lineof(getter), getter.toString(), lineof(setter), setter.toString() + diff --git a/python/ql/test/library-tests/descriptors/test.py b/python/ql/test/library-tests/descriptors/test.py new file mode 100644 index 000000000000..8d7f14198c70 --- /dev/null +++ b/python/ql/test/library-tests/descriptors/test.py @@ -0,0 +1,34 @@ + + + +class C(object): + + @property + def f(self): + return self._f + + @f.setter + def f(self): + return self._f + +class D(object): + + @classmethod + def c1(self): + pass + + def c2(self): + pass + + c3 = classmethod(c2) + c2 = classmethod(c2) + + @staticmethod + def s1(self): + pass + + def s2(self): + pass + + s3 = staticmethod(s2) + s2 = staticmethod(s2) diff --git a/python/ql/test/library-tests/encoding/CheckEncoding.expected b/python/ql/test/library-tests/encoding/CheckEncoding.expected new file mode 100644 index 000000000000..686ad385436c --- /dev/null +++ b/python/ql/test/library-tests/encoding/CheckEncoding.expected @@ -0,0 +1,4 @@ +| latin.py | latin1 | +| shift_jis.py | shift-jis | +| utf8.py | utf-8 | +| utf8_bom.py | none | diff --git a/python/ql/test/library-tests/encoding/CheckEncoding.ql b/python/ql/test/library-tests/encoding/CheckEncoding.ql new file mode 100644 index 000000000000..2b0af6ee84a6 --- /dev/null +++ b/python/ql/test/library-tests/encoding/CheckEncoding.ql @@ -0,0 +1,8 @@ +import python + +from File f, string encoding +where +encoding = f.getSpecifiedEncoding() +or +not exists(f.getSpecifiedEncoding()) and encoding = "none" +select f.getName(), encoding diff --git a/python/ql/test/library-tests/encoding/latin.py b/python/ql/test/library-tests/encoding/latin.py new file mode 100644 index 000000000000..538a7b90e932 --- /dev/null +++ b/python/ql/test/library-tests/encoding/latin.py @@ -0,0 +1,4 @@ +"Any old stuff can go here" +# -*- coding: latin1 -*- +# Günter + diff --git a/python/ql/test/library-tests/encoding/shift_jis.py b/python/ql/test/library-tests/encoding/shift_jis.py new file mode 100644 index 000000000000..89b7b91fd8c1 --- /dev/null +++ b/python/ql/test/library-tests/encoding/shift_jis.py @@ -0,0 +1,11 @@ +# encoding:shift-jis + +#This is copied from the Python test library copyright PSF. + +""" +Python ‚ÌŠJ”­‚ÍA1990 ”N‚²‚ë‚©‚çŠJŽn‚³‚ê‚Ä‚¢‚Ü‚·B +ŠJ”­ŽÒ‚Ì Guido van Rossum ‚Í‹³ˆç—p‚̃vƒƒOƒ‰ƒ~ƒ“ƒOŒ¾ŒêuABCv‚ÌŠJ”­‚ÉŽQ‰Á‚µ‚Ä‚¢‚Ü‚µ‚½‚ªAABC ‚ÍŽÀ—pã‚Ì–Ú“I‚ɂ͂ ‚Ü‚è“K‚µ‚Ä‚¢‚Ü‚¹‚ñ‚Å‚µ‚½B +‚±‚̂悤‚È”wŒi‚©‚綂܂ꂽ Python ‚ÌŒ¾ŒêÝŒv‚ÍAuƒVƒ“ƒvƒ‹v‚ÅuK“¾‚ª—eˆÕv‚Æ‚¢‚¤–Ú•W‚Éd“_‚ª’u‚©‚ê‚Ä‚¢‚Ü‚·B +‘½‚­‚̃XƒNƒŠƒvƒgŒnŒ¾Œê‚ł̓†[ƒU‚Ì–Úæ‚Ì—˜•Ö«‚ð—D悵‚ÄFX‚È‹@”\‚ðŒ¾Œê—v‘f‚Æ‚µ‚ÄŽæ‚è“ü‚ê‚éꇂª‘½‚¢‚̂ł·‚ªAPython ‚ł͂»‚¤‚¢‚Á‚½¬×H‚ª’ljÁ‚³‚ê‚邱‚Ƃ͂ ‚܂肠‚è‚Ü‚¹‚ñB +Œ¾ŒêŽ©‘̂̋@”\‚ÍŬŒÀ‚ɉŸ‚³‚¦A•K—v‚È‹@”\‚ÍŠg’£ƒ‚ƒWƒ…[ƒ‹‚Æ‚µ‚ĒljÁ‚·‚éA‚Æ‚¢‚¤‚Ì‚ª Python ‚̃|ƒŠƒV[‚Å‚·B +""" diff --git a/python/ql/test/library-tests/encoding/utf8.py b/python/ql/test/library-tests/encoding/utf8.py new file mode 100644 index 000000000000..f440c6944d92 --- /dev/null +++ b/python/ql/test/library-tests/encoding/utf8.py @@ -0,0 +1,2 @@ +# Some abitrary prefix with no space beforecoding: utf-8 -*- +# €€€€ diff --git a/python/ql/test/library-tests/encoding/utf8_bom.py b/python/ql/test/library-tests/encoding/utf8_bom.py new file mode 100644 index 000000000000..509f44c690e3 --- /dev/null +++ b/python/ql/test/library-tests/encoding/utf8_bom.py @@ -0,0 +1 @@ +#Starts with a BOM diff --git a/python/ql/test/library-tests/exceptions/Handles.expected b/python/ql/test/library-tests/exceptions/Handles.expected new file mode 100644 index 000000000000..d3f408de146e --- /dev/null +++ b/python/ql/test/library-tests/exceptions/Handles.expected @@ -0,0 +1,10 @@ +| 37 | ControlFlowNode for ExceptStmt | class Exception3 | +| 39 | ControlFlowNode for ExceptStmt | class Exception2 | +| 41 | ControlFlowNode for ExceptStmt | class Exception1 | +| 43 | ControlFlowNode for ExceptStmt | class NotException2 | +| 47 | ControlFlowNode for ExceptStmt | builtin-class BaseException | +| 58 | ControlFlowNode for ExceptStmt | class Exception3 | +| 60 | ControlFlowNode for ExceptStmt | class NotException2 | +| 62 | ControlFlowNode for ExceptStmt | class InnerException5 | +| 68 | ControlFlowNode for ExceptStmt | builtin-class IndexError | +| 70 | ControlFlowNode for ExceptStmt | class Exception1 | diff --git a/python/ql/test/library-tests/exceptions/Handles.ql b/python/ql/test/library-tests/exceptions/Handles.ql new file mode 100644 index 000000000000..b3e9f7927447 --- /dev/null +++ b/python/ql/test/library-tests/exceptions/Handles.ql @@ -0,0 +1,5 @@ +import python + +from ExceptFlowNode ex, Object t +where ex.handledException(t, _, _) +select ex.getLocation().getStartLine(), ex.toString(), t.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/exceptions/Legal.expected b/python/ql/test/library-tests/exceptions/Legal.expected new file mode 100644 index 000000000000..1b6fd6ec1c4c --- /dev/null +++ b/python/ql/test/library-tests/exceptions/Legal.expected @@ -0,0 +1,12 @@ +| class Exception1 | yes | +| class Exception2 | yes | +| class Exception3 | yes | +| class InnerException1 | yes | +| class InnerException2 | yes | +| class InnerException3 | yes | +| class InnerException4 | yes | +| class InnerException5 | yes | +| class InnerNotException1 | no | +| class InnerNotException2 | no | +| class NotException1 | no | +| class NotException2 | no | \ No newline at end of file diff --git a/python/ql/test/library-tests/exceptions/Legal.ql b/python/ql/test/library-tests/exceptions/Legal.ql new file mode 100644 index 000000000000..37488eb082be --- /dev/null +++ b/python/ql/test/library-tests/exceptions/Legal.ql @@ -0,0 +1,11 @@ +import python + +from ClassObject cls, string legal +where +not cls.isC() and cls.isLegalExceptionType() and legal = "yes" and not cls.failedInference() +or +not cls.isC() and not cls.isLegalExceptionType() and legal = "no" and not cls.failedInference() +or +not cls.isC() and cls.failedInference(legal) + +select cls.toString(), legal diff --git a/python/ql/test/library-tests/exceptions/test.py b/python/ql/test/library-tests/exceptions/test.py new file mode 100644 index 000000000000..bb708a2954e0 --- /dev/null +++ b/python/ql/test/library-tests/exceptions/test.py @@ -0,0 +1,71 @@ + +class NotException1(object): + pass + +class NotException2(object): + pass + + +class Exception1(BaseException): + pass + +class Exception2(KeyError): + pass + +class Exception3(Exception2): + pass + +def f(): + class InnerNotException1(object): + pass + + class InnerNotException2(object): + pass + + + class InnerException1(BaseException): + pass + + class InnerException2(KeyError): + pass + + class InnerException3(Exception2): + pass + +try: + some_call() +except Exception3: + pass +except Exception2: + pass +except Exception1: + pass +except NotException2: + pass +except UndefinedSymbol: + pass +except: + pass + + +def g(): + class InnerException4(Exception): + pass + class InnerException5(InnerException4): + pass + try: + some_call() + except Exception3: + pass + except NotException2: + pass + except InnerException5: + pass + +def h(seq): + try: + [x[0] for x in seq] + except IndexError: + pass + except Exception1: + pass diff --git a/python/ql/test/library-tests/exprs/AstParent.expected b/python/ql/test/library-tests/exprs/AstParent.expected new file mode 100644 index 000000000000..f082a67fcf66 --- /dev/null +++ b/python/ql/test/library-tests/exprs/AstParent.expected @@ -0,0 +1 @@ +| 0 | diff --git a/python/ql/test/library-tests/exprs/AstParent.ql b/python/ql/test/library-tests/exprs/AstParent.ql new file mode 100644 index 000000000000..3e26f6723602 --- /dev/null +++ b/python/ql/test/library-tests/exprs/AstParent.ql @@ -0,0 +1,6 @@ +import python + +select +count(AstNode c | not exists(c.getParentNode()) and not c instanceof Module) ++ +count(AstNode c | strictcount(c.getParentNode()) > 1) \ No newline at end of file diff --git a/python/ql/test/library-tests/exprs/Child.expected b/python/ql/test/library-tests/exprs/Child.expected new file mode 100644 index 000000000000..91c6e7e87d24 --- /dev/null +++ b/python/ql/test/library-tests/exprs/Child.expected @@ -0,0 +1,34 @@ +| 0 | Module exprs_test | 2 | exprs_test.py:2:1:2:1 | ExprStmt | +| 0 | Module exprs_test | 3 | exprs_test.py:3:1:3:5 | ExprStmt | +| 0 | Module exprs_test | 4 | exprs_test.py:4:1:4:6 | ExprStmt | +| 0 | Module exprs_test | 5 | exprs_test.py:5:1:5:9 | ExprStmt | +| 0 | Module exprs_test | 6 | exprs_test.py:6:1:6:5 | AssignStmt | +| 0 | Module exprs_test | 7 | exprs_test.py:7:1:7:1 | ExprStmt | +| 0 | Module exprs_test | 8 | exprs_test.py:8:1:8:12 | ExprStmt | +| 0 | Module exprs_test | 9 | exprs_test.py:9:1:9:11 | ExprStmt | +| 0 | Module exprs_test | 11 | exprs_test.py:11:1:11:11 | ExprStmt | +| 2 | ExprStmt | 2 | exprs_test.py:2:1:2:1 | IntegerLiteral | +| 3 | ExprStmt | 3 | exprs_test.py:3:2:3:4 | Tuple | +| 3 | Tuple | 3 | exprs_test.py:3:2:3:2 | IntegerLiteral | +| 3 | Tuple | 3 | exprs_test.py:3:4:3:4 | IntegerLiteral | +| 4 | ExprStmt | 4 | exprs_test.py:4:1:4:6 | List | +| 4 | List | 4 | exprs_test.py:4:2:4:2 | IntegerLiteral | +| 4 | List | 4 | exprs_test.py:4:5:4:5 | IntegerLiteral | +| 5 | ExprStmt | 5 | exprs_test.py:5:1:5:9 | __debug__ | +| 6 | AssignStmt | 6 | exprs_test.py:6:1:6:1 | x | +| 6 | AssignStmt | 6 | exprs_test.py:6:5:6:5 | IntegerLiteral | +| 7 | ExprStmt | 7 | exprs_test.py:7:1:7:1 | x | +| 8 | Attribute | 8 | exprs_test.py:8:1:8:6 | List | +| 8 | Attribute() | 8 | exprs_test.py:8:1:8:10 | Attribute | +| 8 | ExprStmt | 8 | exprs_test.py:8:1:8:12 | Attribute() | +| 8 | List | 8 | exprs_test.py:8:2:8:2 | IntegerLiteral | +| 8 | List | 8 | exprs_test.py:8:5:8:5 | IntegerLiteral | +| 9 | ExprStmt | 9 | exprs_test.py:9:1:9:11 | Subscript | +| 9 | List | 9 | exprs_test.py:9:2:9:2 | IntegerLiteral | +| 9 | List | 9 | exprs_test.py:9:5:9:6 | IntegerLiteral | +| 9 | Subscript | 9 | exprs_test.py:9:1:9:7 | List | +| 9 | Subscript | 9 | exprs_test.py:9:9:9:10 | IntegerLiteral | +| 11 | DictUnpacking | 11 | exprs_test.py:11:8:11:10 | arg | +| 11 | ExprStmt | 11 | exprs_test.py:11:1:11:11 | func() | +| 11 | func() | 11 | exprs_test.py:11:1:11:4 | func | +| 11 | func() | 11 | exprs_test.py:11:6:11:10 | DictUnpacking | diff --git a/python/ql/test/library-tests/exprs/Child.ql b/python/ql/test/library-tests/exprs/Child.ql new file mode 100644 index 000000000000..0638f6c4e220 --- /dev/null +++ b/python/ql/test/library-tests/exprs/Child.ql @@ -0,0 +1,6 @@ +import python + +from AstNode p, AstNode c +where p.getAChildNode() = c +select p.getLocation().getStartLine(), p.toString(), c.getLocation().getStartLine(), c + diff --git a/python/ql/test/library-tests/exprs/IsConstant.expected b/python/ql/test/library-tests/exprs/IsConstant.expected new file mode 100644 index 000000000000..7f61cee7a65a --- /dev/null +++ b/python/ql/test/library-tests/exprs/IsConstant.expected @@ -0,0 +1,17 @@ +| exprs_test.py:2:1:2:1 | IntegerLiteral | +| exprs_test.py:3:2:3:2 | IntegerLiteral | +| exprs_test.py:3:2:3:4 | Tuple | +| exprs_test.py:3:4:3:4 | IntegerLiteral | +| exprs_test.py:4:1:4:6 | List | +| exprs_test.py:4:2:4:2 | IntegerLiteral | +| exprs_test.py:4:5:4:5 | IntegerLiteral | +| exprs_test.py:6:5:6:5 | IntegerLiteral | +| exprs_test.py:8:1:8:6 | List | +| exprs_test.py:8:1:8:10 | Attribute | +| exprs_test.py:8:2:8:2 | IntegerLiteral | +| exprs_test.py:8:5:8:5 | IntegerLiteral | +| exprs_test.py:9:1:9:7 | List | +| exprs_test.py:9:1:9:11 | Subscript | +| exprs_test.py:9:2:9:2 | IntegerLiteral | +| exprs_test.py:9:5:9:6 | IntegerLiteral | +| exprs_test.py:9:9:9:10 | IntegerLiteral | diff --git a/python/ql/test/library-tests/exprs/IsConstant.ql b/python/ql/test/library-tests/exprs/IsConstant.ql new file mode 100644 index 000000000000..ecef17eb385e --- /dev/null +++ b/python/ql/test/library-tests/exprs/IsConstant.ql @@ -0,0 +1,5 @@ +import python + +from Expr e +where e.isConstant() +select e diff --git a/python/ql/test/library-tests/exprs/exprs_test.py b/python/ql/test/library-tests/exprs/exprs_test.py new file mode 100644 index 000000000000..b7f41ca1e27f --- /dev/null +++ b/python/ql/test/library-tests/exprs/exprs_test.py @@ -0,0 +1,11 @@ + +1 +(2,3) +[4, 5] +__debug__ +x = 6 +x +[7, 8].len() +[9, 10][11] + +func(**arg) diff --git a/python/ql/test/library-tests/filters/generated/Filter.expected b/python/ql/test/library-tests/filters/generated/Filter.expected new file mode 100644 index 000000000000..3ddcffc3d949 --- /dev/null +++ b/python/ql/test/library-tests/filters/generated/Filter.expected @@ -0,0 +1,4 @@ +| generic.py | tools/idna-data | +| sphinx.py | Sphinx | +| swig.py | SWIG | +| thrift.py | Thrift | diff --git a/python/ql/test/library-tests/filters/generated/Filter.ql b/python/ql/test/library-tests/filters/generated/Filter.ql new file mode 100644 index 000000000000..d741328d6f76 --- /dev/null +++ b/python/ql/test/library-tests/filters/generated/Filter.ql @@ -0,0 +1,6 @@ + +import python +import semmle.python.filters.GeneratedCode + +from GeneratedFile f +select f.toString(), f.getTool() diff --git a/python/ql/test/library-tests/filters/generated/generic.py b/python/ql/test/library-tests/filters/generated/generic.py new file mode 100644 index 000000000000..5cdde9b88900 --- /dev/null +++ b/python/ql/test/library-tests/filters/generated/generic.py @@ -0,0 +1 @@ +# This file is automatically generated by tools/idna-data diff --git a/python/ql/test/library-tests/filters/generated/sphinx.py b/python/ql/test/library-tests/filters/generated/sphinx.py new file mode 100644 index 000000000000..8e80f7914e31 --- /dev/null +++ b/python/ql/test/library-tests/filters/generated/sphinx.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Autogenerated by Sphinx on Mon May 16 13:41:38 2016 +topics = {'assert': '\n' } diff --git a/python/ql/test/library-tests/filters/generated/swig.py b/python/ql/test/library-tests/filters/generated/swig.py new file mode 100644 index 000000000000..a8621334476c --- /dev/null +++ b/python/ql/test/library-tests/filters/generated/swig.py @@ -0,0 +1,6 @@ +# This file was automatically generated by SWIG (http://www.swig.org). +# Version 2.0.9 +# +# Do not make changes to this file unless you know what you are doing--modify +# the SWIG interface file instead. + diff --git a/python/ql/test/library-tests/filters/generated/thrift.py b/python/ql/test/library-tests/filters/generated/thrift.py new file mode 100644 index 000000000000..36b7e9e103ac --- /dev/null +++ b/python/ql/test/library-tests/filters/generated/thrift.py @@ -0,0 +1,7 @@ +# +# Autogenerated by Thrift Compiler (0.9.1) +# +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING +# +# options string: py:utf8strings +# diff --git a/python/ql/test/library-tests/filters/tests/Filter.expected b/python/ql/test/library-tests/filters/tests/Filter.expected new file mode 100644 index 000000000000..7e2143c9ac4f --- /dev/null +++ b/python/ql/test/library-tests/filters/tests/Filter.expected @@ -0,0 +1,3 @@ +| Class MyTest | +| Function test_1 | +| Function test_2 | diff --git a/python/ql/test/library-tests/filters/tests/Filter.ql b/python/ql/test/library-tests/filters/tests/Filter.ql new file mode 100644 index 000000000000..e20231ea5fa7 --- /dev/null +++ b/python/ql/test/library-tests/filters/tests/Filter.ql @@ -0,0 +1,6 @@ + +import python +import semmle.python.filters.Tests + +from TestScope t +select t.toString() diff --git a/python/ql/test/library-tests/filters/tests/test.py b/python/ql/test/library-tests/filters/tests/test.py new file mode 100644 index 000000000000..cfcdc56ba647 --- /dev/null +++ b/python/ql/test/library-tests/filters/tests/test.py @@ -0,0 +1,12 @@ + + +class TestCase: + pass + +class MyTest(TestCase): + + def test_1(self): + pass + + def test_2(self): + pass diff --git a/python/ql/test/library-tests/formatting/FormatArguments.expected b/python/ql/test/library-tests/formatting/FormatArguments.expected new file mode 100755 index 000000000000..0a76d0c99235 --- /dev/null +++ b/python/ql/test/library-tests/formatting/FormatArguments.expected @@ -0,0 +1,24 @@ +| 3 | {name!r}, {0} | 0 | 8 | 'name' | +| 3 | {name!r}, {0} | 10 | 13 | 0 | +| 4 | {0}, {1} | 0 | 3 | 0 | +| 4 | {0}, {1} | 5 | 8 | 1 | +| 5 | {}, {} | 0 | 2 | 0 | +| 5 | {}, {} | 4 | 6 | 1 | +| 8 | {{}}> | 10 | 12 | 0 | +| 10 | {{0}}{0} | 5 | 8 | 0 | +| 11 | {{{}}} | 2 | 4 | 0 | +| 13 | { {{ 0} }} | 0 | 7 | ' {{ 0' | +| 14 | { { { 0} }} | 4 | 8 | ' 0' | +| 15 | {{{{{} | 4 | 6 | 0 | +| 16 | {}\r{}{:<{width}} | 0 | 2 | 0 | +| 16 | {}\r{}{:<{width}} | 3 | 5 | 1 | +| 16 | {}\r{}{:<{width}} | 5 | 16 | 2 | +| 16 | {}\r{}{:<{width}} | 8 | 15 | 'width' | +| 17 | {}\r{}{:<{}} | 0 | 2 | 0 | +| 17 | {}\r{}{:<{}} | 3 | 5 | 1 | +| 17 | {}\r{}{:<{}} | 5 | 11 | 2 | +| 17 | {}\r{}{:<{}} | 8 | 10 | 3 | +| 19 | {x:0.{decimals}f} | 0 | 17 | 'x' | +| 19 | {x:0.{decimals}f} | 5 | 15 | 'decimals' | +| 21 | invalid value of type {.__name__}: {} | 22 | 33 | 0 | +| 21 | invalid value of type {.__name__}: {} | 35 | 37 | 1 | diff --git a/python/ql/test/library-tests/formatting/FormatArguments.ql b/python/ql/test/library-tests/formatting/FormatArguments.ql new file mode 100644 index 000000000000..19e47b7fc44b --- /dev/null +++ b/python/ql/test/library-tests/formatting/FormatArguments.ql @@ -0,0 +1,10 @@ + +import python +import Expressions.Formatting.AdvancedFormatting + +from AdvancedFormatString a, string name, int start, int end +where +name = "'" + a.getFieldName(start, end) + "'" +or +name = a.getFieldNumber(start, end).toString() +select a.getLocation().getStartLine(), a.getText(), start, end, name diff --git a/python/ql/test/library-tests/formatting/FormatFields.expected b/python/ql/test/library-tests/formatting/FormatFields.expected new file mode 100755 index 000000000000..a3ff555b02f0 --- /dev/null +++ b/python/ql/test/library-tests/formatting/FormatFields.expected @@ -0,0 +1,24 @@ +| 3 | {name!r}, {0} | 0 | 8 | {name!r} | +| 3 | {name!r}, {0} | 10 | 13 | {0} | +| 4 | {0}, {1} | 0 | 3 | {0} | +| 4 | {0}, {1} | 5 | 8 | {1} | +| 5 | {}, {} | 0 | 2 | {} | +| 5 | {}, {} | 4 | 6 | {} | +| 8 | {{}}> | 10 | 12 | {} | +| 10 | {{0}}{0} | 5 | 8 | {0} | +| 11 | {{{}}} | 2 | 4 | {} | +| 13 | { {{ 0} }} | 0 | 7 | { {{ 0} | +| 14 | { { { 0} }} | 4 | 8 | { 0} | +| 15 | {{{{{} | 4 | 6 | {} | +| 16 | {}\r{}{:<{width}} | 0 | 2 | {} | +| 16 | {}\r{}{:<{width}} | 3 | 5 | {} | +| 16 | {}\r{}{:<{width}} | 5 | 16 | {:<{width}} | +| 16 | {}\r{}{:<{width}} | 8 | 15 | {width} | +| 17 | {}\r{}{:<{}} | 0 | 2 | {} | +| 17 | {}\r{}{:<{}} | 3 | 5 | {} | +| 17 | {}\r{}{:<{}} | 5 | 11 | {:<{}} | +| 17 | {}\r{}{:<{}} | 8 | 10 | {} | +| 19 | {x:0.{decimals}f} | 0 | 17 | {x:0.{decimals}f} | +| 19 | {x:0.{decimals}f} | 5 | 15 | {decimals} | +| 21 | invalid value of type {.__name__}: {} | 22 | 33 | {.__name__} | +| 21 | invalid value of type {.__name__}: {} | 35 | 37 | {} | diff --git a/python/ql/test/library-tests/formatting/FormatFields.ql b/python/ql/test/library-tests/formatting/FormatFields.ql new file mode 100644 index 000000000000..b8a3b9133555 --- /dev/null +++ b/python/ql/test/library-tests/formatting/FormatFields.ql @@ -0,0 +1,6 @@ + +import python +import Expressions.Formatting.AdvancedFormatting + +from AdvancedFormatString a, int start, int end +select a.getLocation().getStartLine(), a.getText(), start, end, a.getField(start, end) diff --git a/python/ql/test/library-tests/formatting/test.py b/python/ql/test/library-tests/formatting/test.py new file mode 100755 index 000000000000..54ca7e8e94b9 --- /dev/null +++ b/python/ql/test/library-tests/formatting/test.py @@ -0,0 +1,21 @@ +#Simple cases + +"{name!r}, {0}".format() +"{0}, {1}".format() +"{}, {}".format() + +#Complex cases +"{{}}>".format(html_class) + +"{{0}}{0}".format("X") +"{{{}}}".format("X") + +"{ {{ 0} }}".format("X") +"{ { { 0} }}".format("X") +"{{{{{}".format("X") +u'{}\r{}{:<{width}}'.format(1, 2, 3, width=msg_width) +u'{}\r{}{:<{}}'.format(1, 2, 3, 4) +#ODASA 6428 +'{x:0.{decimals}f}'.format(x=x, decimals=int(decimals)) + +"invalid value of type {.__name__}: {}".format(int, 1) diff --git a/python/ql/test/library-tests/imports/Alias.expected b/python/ql/test/library-tests/imports/Alias.expected new file mode 100644 index 000000000000..6ea5e16c29cf --- /dev/null +++ b/python/ql/test/library-tests/imports/Alias.expected @@ -0,0 +1,3 @@ +| Alias | a | a | +| Alias | b | b | +| Alias | c | d | \ No newline at end of file diff --git a/python/ql/test/library-tests/imports/Alias.ql b/python/ql/test/library-tests/imports/Alias.ql new file mode 100644 index 000000000000..5a7c034d02a8 --- /dev/null +++ b/python/ql/test/library-tests/imports/Alias.ql @@ -0,0 +1,5 @@ +import python + +from Alias a, ImportMember i +where i = a.getValue() +select a.toString(), i.getName(), a.getAsname().toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/imports/test.py b/python/ql/test/library-tests/imports/test.py new file mode 100644 index 000000000000..b4ef0503ff64 --- /dev/null +++ b/python/ql/test/library-tests/imports/test.py @@ -0,0 +1,6 @@ + +from x.y.z import ( + a, + b as b, + c as d +) diff --git a/python/ql/test/library-tests/jump_to_defn/Remote.expected b/python/ql/test/library-tests/jump_to_defn/Remote.expected new file mode 100755 index 000000000000..c8c85fb44284 --- /dev/null +++ b/python/ql/test/library-tests/jump_to_defn/Remote.expected @@ -0,0 +1,16 @@ +| module | +| module/C | +| module/C.m | +| module/C.sm | +| module/f | +| test | +| test/BaseClass | +| test/C | +| test/D | +| test/D.cls_attr | +| test/D.meth | +| test/DerivedClass | +| test/f | +| test/func | +| test/module | +| test/no_phi_defn | diff --git a/python/ql/test/library-tests/jump_to_defn/Remote.ql b/python/ql/test/library-tests/jump_to_defn/Remote.ql new file mode 100644 index 000000000000..18b0ebacdc0c --- /dev/null +++ b/python/ql/test/library-tests/jump_to_defn/Remote.ql @@ -0,0 +1,10 @@ + +import python +import analysis.DefinitionTracking +import analysis.CrossProjectDefinitions + +from Definition defn, Symbol s +where s.find() = defn.getAstNode() and +// Exclude dunder names as these vary from version to version. +not s.toString().regexpMatch(".+__") +select s.toString() diff --git a/python/ql/test/library-tests/jump_to_defn/Sanity.expected b/python/ql/test/library-tests/jump_to_defn/Sanity.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/jump_to_defn/Sanity.ql b/python/ql/test/library-tests/jump_to_defn/Sanity.ql new file mode 100644 index 000000000000..0e4455ab09b7 --- /dev/null +++ b/python/ql/test/library-tests/jump_to_defn/Sanity.ql @@ -0,0 +1,22 @@ + +import python +import analysis.DefinitionTracking +import analysis.CrossProjectDefinitions + +predicate local_problem(Definition defn, string issue, string repr) { + not exists(defn.toString()) and issue = "no toString()" and repr = "a local definition" + or + not exists(defn.getAstNode()) and issue = "no getAstNode()" and repr = defn.toString() + or + not exists(defn.getLocation()) and issue = "no getLocation()" and repr = defn.toString() + or + count(defn.getLocation())> 1 and issue = "more than one getLocation()" and repr = defn.toString() +} + +predicate remote_problem(Symbol s, string issue, string repr) { + not exists(s.toString()) and issue = "no toString()" and repr = "a symbol" +} + +from string issue, string repr +where remote_problem(_, issue, repr) or local_problem(_, issue, repr) +select issue, repr diff --git a/python/ql/test/library-tests/jump_to_defn/Symbol.expected b/python/ql/test/library-tests/jump_to_defn/Symbol.expected new file mode 100644 index 000000000000..5e576517552b --- /dev/null +++ b/python/ql/test/library-tests/jump_to_defn/Symbol.expected @@ -0,0 +1,21 @@ +| module | module.py:0 | +| module/C | module.py:2 | +| module/C.m | module.py:8 | +| module/C.sm | module.py:4 | +| module/__name__ | module.py:0 | +| module/f | module.py:11 | +| test | test.py:0 | +| test/BaseClass | test.py:36 | +| test/BaseClass.__init__ | test.py:38 | +| test/C | module.py:2 | +| test/D | test.py:21 | +| test/D.__init__ | test.py:27 | +| test/D.cls_attr | test.py:24 | +| test/D.meth | test.py:30 | +| test/DerivedClass | test.py:41 | +| test/DerivedClass.__init__ | test.py:43 | +| test/__name__ | test.py:0 | +| test/f | module.py:11 | +| test/func | test.py:6 | +| test/module | test.py:2 | +| test/no_phi_defn | test.py:14 | diff --git a/python/ql/test/library-tests/jump_to_defn/Symbol.ql b/python/ql/test/library-tests/jump_to_defn/Symbol.ql new file mode 100644 index 000000000000..7f111863b069 --- /dev/null +++ b/python/ql/test/library-tests/jump_to_defn/Symbol.ql @@ -0,0 +1,8 @@ + +import python +import analysis.CrossProjectDefinitions + +from Symbol symbol + +select symbol.toString(), symbol.find().getLocation().toString() + diff --git a/python/ql/test/library-tests/jump_to_defn/module.py b/python/ql/test/library-tests/jump_to_defn/module.py new file mode 100644 index 000000000000..1c5102fe4056 --- /dev/null +++ b/python/ql/test/library-tests/jump_to_defn/module.py @@ -0,0 +1,13 @@ + +class C(object): + + @staticmethod + def sm(arg): + pass + + def m(self, arg): + pass + +def f(arg): + pass + diff --git a/python/ql/test/library-tests/jump_to_defn/test.expected b/python/ql/test/library-tests/jump_to_defn/test.expected new file mode 100755 index 000000000000..183908d58963 --- /dev/null +++ b/python/ql/test/library-tests/jump_to_defn/test.expected @@ -0,0 +1,41 @@ +| test.py:2 | ImportExpr | Definition module.py:0 | +| test.py:3 | ImportExpr | Definition module.py:0 | +| test.py:3 | ImportMember | Definition module.py:2 | +| test.py:4 | ImportExpr | Definition module.py:0 | +| test.py:4 | ImportMember | Definition module.py:11 | +| test.py:7 | Attribute | Definition module.py:4 | +| test.py:7 | C | Definition test.py:3 | +| test.py:7 | arg | Definition test.py:6 | +| test.py:8 | Attribute | Definition module.py:8 | +| test.py:8 | C | Definition test.py:3 | +| test.py:8 | arg | Definition test.py:6 | +| test.py:9 | arg | Definition test.py:6 | +| test.py:9 | f | Definition test.py:4 | +| test.py:10 | Attribute | Definition module.py:2 | +| test.py:10 | Attribute | Definition module.py:4 | +| test.py:10 | arg | Definition test.py:6 | +| test.py:10 | module | Definition test.py:2 | +| test.py:11 | Attribute | Definition module.py:2 | +| test.py:11 | Attribute | Definition module.py:8 | +| test.py:11 | arg | Definition test.py:6 | +| test.py:11 | module | Definition test.py:2 | +| test.py:12 | Attribute | Definition module.py:11 | +| test.py:12 | arg | Definition test.py:6 | +| test.py:12 | module | Definition test.py:2 | +| test.py:15 | seq | Definition test.py:14 | +| test.py:16 | cond | Definition test.py:14 | +| test.py:17 | seq | Definition test.py:14 | +| test.py:19 | x | Definition test.py:15 | +| test.py:19 | x | Definition test.py:17 | +| test.py:31 | Attribute | Definition test.py:24 | +| test.py:31 | self | Definition test.py:30 | +| test.py:33 | Attribute | Definition test.py:24 | +| test.py:33 | D | Definition test.py:21 | +| test.py:41 | BaseClass | Definition test.py:36 | +| test.py:44 | Attribute | Definition test.py:38 | +| test.py:44 | BaseClass | Definition test.py:36 | +| test.py:44 | self | Definition test.py:43 | +| test.py:45 | Attribute | Definition test.py:38 | +| test.py:45 | DerivedClass | Definition test.py:41 | +| test.py:45 | self | Definition test.py:43 | +| test.py:46 | m | Definition test.py:45 | diff --git a/python/ql/test/library-tests/jump_to_defn/test.py b/python/ql/test/library-tests/jump_to_defn/test.py new file mode 100644 index 000000000000..c14cb6436bcb --- /dev/null +++ b/python/ql/test/library-tests/jump_to_defn/test.py @@ -0,0 +1,47 @@ + +import module +from module import C +from module import f + +def func(arg): + C.sm(arg) + C().m(arg) + f(arg) + module.C.sm(arg) + module.C().m(arg) + module.f(arg) + +def no_phi_defn(seq, cond): + x = seq[0] + if cond: + x = seq[1] + pass + x + +class D(object): + + cls_attr = ( + 3 + ) + + def __init__(self): + pass + + def meth(self): + return self.cls_attr + +D.cls_attr + + +class BaseClass(object): + + def __init__(self): + pass + +class DerivedClass(BaseClass): + + def __init__(self): + BaseClass.__init__(self) + m = super(DerivedClass, self).__init__ + m() + diff --git a/python/ql/test/library-tests/jump_to_defn/test.ql b/python/ql/test/library-tests/jump_to_defn/test.ql new file mode 100644 index 000000000000..ed8bf8ab84c8 --- /dev/null +++ b/python/ql/test/library-tests/jump_to_defn/test.ql @@ -0,0 +1,11 @@ +/** + * @name test + */ + +import python +import analysis.DefinitionTracking + +from Expr use, Definition defn +where defn = getADefinition(use) +and use.getEnclosingModule().getName() = "test" +select use.getLocation().toString(), use.toString(), defn.toString() diff --git a/python/ql/test/library-tests/locations/elif/test.expected b/python/ql/test/library-tests/locations/elif/test.expected new file mode 100644 index 000000000000..f6682119ad12 --- /dev/null +++ b/python/ql/test/library-tests/locations/elif/test.expected @@ -0,0 +1,18 @@ +| If | 3 | 1 | 3 | 5 | +| If | 5 | 1 | 5 | 7 | +| If | 7 | 1 | 7 | 7 | +| If | 10 | 1 | 10 | 5 | +| If | 12 | 1 | 12 | 7 | +| If | 13 | 5 | 13 | 9 | +| ModuleMetrics | 0 | 0 | 0 | 0 | +| Name | 3 | 4 | 3 | 4 | +| Name | 5 | 6 | 5 | 6 | +| Name | 7 | 6 | 7 | 6 | +| Name | 10 | 4 | 10 | 4 | +| Name | 12 | 6 | 12 | 6 | +| Name | 13 | 8 | 13 | 8 | +| Pass | 4 | 5 | 4 | 8 | +| Pass | 6 | 5 | 6 | 8 | +| Pass | 8 | 5 | 8 | 8 | +| Pass | 11 | 5 | 11 | 8 | +| Pass | 14 | 9 | 14 | 12 | diff --git a/python/ql/test/library-tests/locations/elif/test.py b/python/ql/test/library-tests/locations/elif/test.py new file mode 100644 index 000000000000..eeaf2828457e --- /dev/null +++ b/python/ql/test/library-tests/locations/elif/test.py @@ -0,0 +1,14 @@ + +#Elif +if a: + pass +elif b: + pass +elif c: + pass + +if x: + pass +elif y: + if z: + pass diff --git a/python/ql/test/library-tests/locations/elif/test.ql b/python/ql/test/library-tests/locations/elif/test.ql new file mode 100644 index 000000000000..ca7177e847c6 --- /dev/null +++ b/python/ql/test/library-tests/locations/elif/test.ql @@ -0,0 +1,5 @@ +import python + +from AstNode ast, Location l +where ast.getLocation() = l +select ast.getAQlClass(), l.getStartLine(), l.getStartColumn(), l.getEndLine(), l.getEndColumn() \ No newline at end of file diff --git a/python/ql/test/library-tests/locations/implicit_concatenation/part_locations.expected b/python/ql/test/library-tests/locations/implicit_concatenation/part_locations.expected new file mode 100644 index 000000000000..ef11cdc0d363 --- /dev/null +++ b/python/ql/test/library-tests/locations/implicit_concatenation/part_locations.expected @@ -0,0 +1,14 @@ +| 2 | "Hello " | 1 | 8 | +| 2 | "World" | 14 | 20 | +| 5 | "Goodbye " | 1 | 10 | +| 6 | "World" | 1 | 7 | +| 9 | "a" | 3 | 5 | +| 9 | "b" | 7 | 9 | +| 12 | "c" | 2 | 4 | +| 12 | "d" | 6 | 8 | +| 16 | 'e' | 5 | 7 | +| 17 | 'f' | 5 | 7 | +| 32 | '' | 8 | 9 | +| 32 | 'word' | 1 | 6 | +| 35 | '0' | 12 | 14 | +| 35 | '\\n\\n\\n\\n' | 1 | 10 | diff --git a/python/ql/test/library-tests/locations/implicit_concatenation/part_locations.ql b/python/ql/test/library-tests/locations/implicit_concatenation/part_locations.ql new file mode 100644 index 000000000000..2687a785f1b5 --- /dev/null +++ b/python/ql/test/library-tests/locations/implicit_concatenation/part_locations.ql @@ -0,0 +1,12 @@ +import python + +class ImplicitConcat extends StrConst { + ImplicitConcat() { + exists(this.getAnImplicitlyConcatenatedPart()) + } +} + +from StringPart s + + +select s.getLocation().getStartLine(), s.getText(), s.getLocation().getStartColumn(), s.getLocation().getEndColumn() \ No newline at end of file diff --git a/python/ql/test/library-tests/locations/implicit_concatenation/parts.expected b/python/ql/test/library-tests/locations/implicit_concatenation/parts.expected new file mode 100644 index 000000000000..e1d59b7a32d0 --- /dev/null +++ b/python/ql/test/library-tests/locations/implicit_concatenation/parts.expected @@ -0,0 +1,14 @@ +| 2 | Hello World | 0 | "Hello " | +| 2 | Hello World | 1 | "World" | +| 5 | Goodbye World | 0 | "Goodbye " | +| 5 | Goodbye World | 1 | "World" | +| 9 | ab | 0 | "a" | +| 9 | ab | 1 | "b" | +| 12 | cd | 0 | "c" | +| 12 | cd | 1 | "d" | +| 16 | ef | 0 | 'e' | +| 16 | ef | 1 | 'f' | +| 32 | word | 0 | 'word' | +| 32 | word | 1 | '' | +| 35 | \n\n\n\n0 | 0 | '\\n\\n\\n\\n' | +| 35 | \n\n\n\n0 | 1 | '0' | diff --git a/python/ql/test/library-tests/locations/implicit_concatenation/parts.ql b/python/ql/test/library-tests/locations/implicit_concatenation/parts.ql new file mode 100644 index 000000000000..1b1a0d492b32 --- /dev/null +++ b/python/ql/test/library-tests/locations/implicit_concatenation/parts.ql @@ -0,0 +1,14 @@ +import python + +class ImplicitConcat extends StrConst { + ImplicitConcat() { + exists(this.getAnImplicitlyConcatenatedPart()) + } +} + +from StrConst s, StringPart part, int n +where + part = s.getImplicitlyConcatenatedPart(n) + + +select s.getLocation().getStartLine(), s.getText(), n, part.getText() \ No newline at end of file diff --git a/python/ql/test/library-tests/locations/implicit_concatenation/test.expected b/python/ql/test/library-tests/locations/implicit_concatenation/test.expected new file mode 100644 index 000000000000..ae3794a92784 --- /dev/null +++ b/python/ql/test/library-tests/locations/implicit_concatenation/test.expected @@ -0,0 +1,10 @@ +| 2 | Hello World | true | 11 | 1 | 20 | +| 5 | Goodbye World | true | 13 | 1 | 7 | +| 9 | ab | true | 2 | 3 | 9 | +| 12 | cd | true | 2 | 2 | 8 | +| 16 | ef | true | 2 | 5 | 7 | +| 21 | string | false | 6 | 1 | 8 | +| 24 | \n\n\n\n | false | 4 | 1 | 10 | +| 27 | \u0123\u1234 | false | 2 | 1 | 15 | +| 32 | word | true | 4 | 1 | 9 | +| 35 | \n\n\n\n0 | true | 5 | 1 | 14 | diff --git a/python/ql/test/library-tests/locations/implicit_concatenation/test.py b/python/ql/test/library-tests/locations/implicit_concatenation/test.py new file mode 100644 index 000000000000..76dd43add805 --- /dev/null +++ b/python/ql/test/library-tests/locations/implicit_concatenation/test.py @@ -0,0 +1,36 @@ +#Single line concat +"Hello " "World" + +#Multi line concat with line continuation +"Goodbye " \ +"World" + +#Single line concat in list +[ "a" "b" ] + +#Single line, looks like tuple, but is just parenthesized +("c" "d" ) + +#Multi line in list +[ + 'e' + 'f' +] + +#Simple String +"string" + +#String with escapes +"\n\n\n\n" + +#String with unicode escapes +u'\u0123\u1234' + +#These implicit concatenations can only be found with extractor support. + +#Concat with empty String +'word' '' + +#String with escapes and concatenation : +'\n\n\n\n' '0' + diff --git a/python/ql/test/library-tests/locations/implicit_concatenation/test.ql b/python/ql/test/library-tests/locations/implicit_concatenation/test.ql new file mode 100644 index 000000000000..5b2f6ae0a552 --- /dev/null +++ b/python/ql/test/library-tests/locations/implicit_concatenation/test.ql @@ -0,0 +1,16 @@ +import python + +class ImplicitConcat extends StrConst { + ImplicitConcat() { + exists(this.getAnImplicitlyConcatenatedPart()) + } +} + +from StrConst s, boolean isConcat +where + s instanceof ImplicitConcat and isConcat = true + or + not s instanceof ImplicitConcat and isConcat = false + + +select s.getLocation().getStartLine(), s.getText(), isConcat, s.getText().length(), s.getLocation().getStartColumn(), s.getLocation().getEndColumn() \ No newline at end of file diff --git a/python/ql/test/library-tests/locations/negative_numbers/negative.expected b/python/ql/test/library-tests/locations/negative_numbers/negative.expected new file mode 100644 index 000000000000..77c824ac91f9 --- /dev/null +++ b/python/ql/test/library-tests/locations/negative_numbers/negative.expected @@ -0,0 +1,26 @@ +| FloatLiteral | 6 | 2 | 6 | 4 | | +| FloatLiteral | 7 | 2 | 7 | 7 | | +| FloatLiteral | 11 | 3 | 11 | 5 | () | +| FloatLiteral | 12 | 3 | 12 | 8 | () | +| FloatLiteral | 16 | 3 | 16 | 5 | | +| FloatLiteral | 17 | 3 | 17 | 8 | | +| ImaginaryLiteral | 19 | 2 | 19 | 3 | | +| IntegerLiteral | 4 | 2 | 4 | 2 | | +| IntegerLiteral | 5 | 2 | 5 | 18 | | +| IntegerLiteral | 9 | 3 | 9 | 3 | () | +| IntegerLiteral | 10 | 3 | 10 | 19 | () | +| IntegerLiteral | 14 | 3 | 14 | 3 | | +| IntegerLiteral | 15 | 3 | 15 | 19 | | +| UnaryExpr | 4 | 1 | 4 | 2 | | +| UnaryExpr | 5 | 1 | 5 | 18 | | +| UnaryExpr | 6 | 1 | 6 | 4 | | +| UnaryExpr | 7 | 1 | 7 | 7 | | +| UnaryExpr | 9 | 1 | 9 | 4 | | +| UnaryExpr | 10 | 1 | 10 | 20 | | +| UnaryExpr | 11 | 1 | 11 | 6 | | +| UnaryExpr | 12 | 1 | 12 | 9 | | +| UnaryExpr | 14 | 2 | 14 | 3 | () | +| UnaryExpr | 15 | 2 | 15 | 19 | () | +| UnaryExpr | 16 | 2 | 16 | 5 | () | +| UnaryExpr | 17 | 2 | 17 | 8 | () | +| UnaryExpr | 19 | 1 | 19 | 3 | | \ No newline at end of file diff --git a/python/ql/test/library-tests/locations/negative_numbers/negative.py b/python/ql/test/library-tests/locations/negative_numbers/negative.py new file mode 100644 index 000000000000..92d46f37b64f --- /dev/null +++ b/python/ql/test/library-tests/locations/negative_numbers/negative.py @@ -0,0 +1,19 @@ + +#Some negative numbers + +-1 +-10000000000000000 +-1.0 +-3.0e17 + +-(1) +-(10000000000000000) +-(1.0) +-(3.0e17) + +(-1) +(-10000000000000000) +(-1.0) +(-3.0e17) + +-1j \ No newline at end of file diff --git a/python/ql/test/library-tests/locations/negative_numbers/negative.ql b/python/ql/test/library-tests/locations/negative_numbers/negative.ql new file mode 100644 index 000000000000..c423cb0532cd --- /dev/null +++ b/python/ql/test/library-tests/locations/negative_numbers/negative.ql @@ -0,0 +1,13 @@ +import python + +from Expr e, int bl, int bc, int el,int ec, string p + +where + e.getLocation().hasLocationInfo(_, bl, bc, el, ec) + and + if e.isParenthesized() then + p = "()" + else + p = "" + +select e.toString(), bl, bc, el, ec, p \ No newline at end of file diff --git a/python/ql/test/library-tests/locations/nested_classes/Test.expected b/python/ql/test/library-tests/locations/nested_classes/Test.expected new file mode 100644 index 000000000000..1469ceebc742 --- /dev/null +++ b/python/ql/test/library-tests/locations/nested_classes/Test.expected @@ -0,0 +1,6 @@ +| A_Simple | 2 | 1 | 2 | 23 | +| B1_Outer | 6 | 1 | 6 | 23 | +| B2_Inner | 8 | 5 | 8 | 27 | +| C1_Outer | 12 | 1 | 12 | 15 | +| C2_Mid__ | 14 | 5 | 14 | 19 | +| C3_Inner | 16 | 9 | 16 | 23 | diff --git a/python/ql/test/library-tests/locations/nested_classes/Test.ql b/python/ql/test/library-tests/locations/nested_classes/Test.ql new file mode 100644 index 000000000000..693d6f7116f5 --- /dev/null +++ b/python/ql/test/library-tests/locations/nested_classes/Test.ql @@ -0,0 +1,7 @@ + +import python + +from Class cls, Location l +where l = cls.getLocation() + +select cls.getName(), l.getStartLine(), l.getStartColumn(), l.getEndLine(), l.getEndColumn() diff --git a/python/ql/test/library-tests/locations/nested_classes/test.py b/python/ql/test/library-tests/locations/nested_classes/test.py new file mode 100644 index 000000000000..af99d3a7500e --- /dev/null +++ b/python/ql/test/library-tests/locations/nested_classes/test.py @@ -0,0 +1,18 @@ + +class A_Simple(object): + + pass + +class B1_Outer(object): + + class B2_Inner(object): + + pass + +class C1_Outer: + + class C2_Mid__: + + class C3_Inner: + + pass diff --git a/python/ql/test/library-tests/modules/overlapping-paths/ModuleNames.expected b/python/ql/test/library-tests/modules/overlapping-paths/ModuleNames.expected new file mode 100644 index 000000000000..e7b438799040 --- /dev/null +++ b/python/ql/test/library-tests/modules/overlapping-paths/ModuleNames.expected @@ -0,0 +1,7 @@ +| outer/inner/imported | imported | +| outer/inner/imported/__init__.py | imported.__init__ | +| outer/inner/imported/x.py | imported.x | +| outer/inner/y.py | y | +| src/package | package | +| src/package/__init__.py | package.__init__ | +| src/package/test.py | package.test | diff --git a/python/ql/test/library-tests/modules/overlapping-paths/ModuleNames.ql b/python/ql/test/library-tests/modules/overlapping-paths/ModuleNames.ql new file mode 100644 index 000000000000..a3a54953513a --- /dev/null +++ b/python/ql/test/library-tests/modules/overlapping-paths/ModuleNames.ql @@ -0,0 +1,5 @@ + +import python + +from Module m +select m.getPath().toString(), m.getName() diff --git a/python/ql/test/library-tests/modules/overlapping-paths/options b/python/ql/test/library-tests/modules/overlapping-paths/options new file mode 100644 index 000000000000..120c64adc17c --- /dev/null +++ b/python/ql/test/library-tests/modules/overlapping-paths/options @@ -0,0 +1 @@ +semmle-extractor-options: -R src --path outer/inner --path outer diff --git a/python/ql/test/library-tests/modules/overlapping-paths/outer/inner/imported/__init__.py b/python/ql/test/library-tests/modules/overlapping-paths/outer/inner/imported/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/modules/overlapping-paths/outer/inner/imported/x.py b/python/ql/test/library-tests/modules/overlapping-paths/outer/inner/imported/x.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/modules/overlapping-paths/outer/inner/y.py b/python/ql/test/library-tests/modules/overlapping-paths/outer/inner/y.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/modules/overlapping-paths/src/package/__init__.py b/python/ql/test/library-tests/modules/overlapping-paths/src/package/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/modules/overlapping-paths/src/package/test.py b/python/ql/test/library-tests/modules/overlapping-paths/src/package/test.py new file mode 100644 index 000000000000..c085d2a59754 --- /dev/null +++ b/python/ql/test/library-tests/modules/overlapping-paths/src/package/test.py @@ -0,0 +1,2 @@ +import imported.x +import y diff --git a/python/ql/test/library-tests/modules/overlapping-paths/src/script.py b/python/ql/test/library-tests/modules/overlapping-paths/src/script.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/modules/spurious_init/ModuleNames.expected b/python/ql/test/library-tests/modules/spurious_init/ModuleNames.expected new file mode 100644 index 000000000000..4aca8d12f078 --- /dev/null +++ b/python/ql/test/library-tests/modules/spurious_init/ModuleNames.expected @@ -0,0 +1,5 @@ +| root | root | +| root/__init__.py | root.__init__ | +| root/src-folder/package | package | +| root/src-folder/package/__init__.py | package.__init__ | +| root/src-folder/package/module.py | package.module | diff --git a/python/ql/test/library-tests/modules/spurious_init/ModuleNames.ql b/python/ql/test/library-tests/modules/spurious_init/ModuleNames.ql new file mode 100644 index 000000000000..a3a54953513a --- /dev/null +++ b/python/ql/test/library-tests/modules/spurious_init/ModuleNames.ql @@ -0,0 +1,5 @@ + +import python + +from Module m +select m.getPath().toString(), m.getName() diff --git a/python/ql/test/library-tests/modules/spurious_init/options b/python/ql/test/library-tests/modules/spurious_init/options new file mode 100644 index 000000000000..dfcf36625238 --- /dev/null +++ b/python/ql/test/library-tests/modules/spurious_init/options @@ -0,0 +1 @@ +semmle-extractor-options: -R . --filter exclude:**/src_archive/** diff --git a/python/ql/test/library-tests/modules/spurious_init/root/__init__.py b/python/ql/test/library-tests/modules/spurious_init/root/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/modules/spurious_init/root/src-folder/package/__init__.py b/python/ql/test/library-tests/modules/spurious_init/root/src-folder/package/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/modules/spurious_init/root/src-folder/package/module.py b/python/ql/test/library-tests/modules/spurious_init/root/src-folder/package/module.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/objects/Literals.expected b/python/ql/test/library-tests/objects/Literals.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/objects/Literals.ql b/python/ql/test/library-tests/objects/Literals.ql new file mode 100644 index 000000000000..f83f4e722da3 --- /dev/null +++ b/python/ql/test/library-tests/objects/Literals.ql @@ -0,0 +1,16 @@ + +/* Test that there are no literals that do not have a corresponding object. */ +import python + + +string repr(Expr e) { + result = e.(Num).getN() or + result = e.(Bytes).getS() or + result = e.(Unicode).getS() +} + +from ImmutableLiteral l +where +not exists(l.getLiteralObject()) + +select l.getLocation().getStartLine(), repr(l) \ No newline at end of file diff --git a/python/ql/test/library-tests/objects/Name.expected b/python/ql/test/library-tests/objects/Name.expected new file mode 100644 index 000000000000..7333ee611e85 --- /dev/null +++ b/python/ql/test/library-tests/objects/Name.expected @@ -0,0 +1,9 @@ +| sys.modules | dict object | +| test.C.cmeth | Function cmeth | +| test.C.cmeth | classmethod() | +| test.C.meth | Function meth | +| test.C.smeth | Function smeth | +| test.C.smeth | staticmethod() | +| test.d | Dict | +| test.l | List | +| test.n | NoneType None | diff --git a/python/ql/test/library-tests/objects/Name.ql b/python/ql/test/library-tests/objects/Name.ql new file mode 100644 index 000000000000..674890c01ba4 --- /dev/null +++ b/python/ql/test/library-tests/objects/Name.ql @@ -0,0 +1,21 @@ + +import python + +from Object o, string name +where o.hasLongName(name) +and ( + name = "sys.modules" + or + name = "test.n" + or + name = "test.l" + or + name = "test.d" + or + name = "test.C.meth" + or + name = "test.C.cmeth" + or + name = "test.C.smeth" +) +select name, o.toString() diff --git a/python/ql/test/library-tests/objects/Strings.expected b/python/ql/test/library-tests/objects/Strings.expected new file mode 100644 index 000000000000..b9d7c336d6b6 --- /dev/null +++ b/python/ql/test/library-tests/objects/Strings.expected @@ -0,0 +1,7 @@ +| test.py:14 | d | +| test.py:15 | | +| test.py:16 | | +| test.py:17 | ' | +| test.py:18 | efwb\nonv\n8979\n | +| test.py:22 | 0 | +| test.py:23 | None | diff --git a/python/ql/test/library-tests/objects/Strings.ql b/python/ql/test/library-tests/objects/Strings.ql new file mode 100644 index 000000000000..9fcceb58fe46 --- /dev/null +++ b/python/ql/test/library-tests/objects/Strings.ql @@ -0,0 +1,8 @@ + +import python + + +from StringObject s, ControlFlowNode f +where f.refersTo(s) +select f.getLocation().toString(), s.getText() + diff --git a/python/ql/test/library-tests/objects/test.py b/python/ql/test/library-tests/objects/test.py new file mode 100644 index 000000000000..d9bc798100ac --- /dev/null +++ b/python/ql/test/library-tests/objects/test.py @@ -0,0 +1,40 @@ +0.058823529630899429 + + +1e-06 +.9999999 +0xffffff +1e10 +0o777 +1. +2.79252680 +0x0001000 +4987312561856745907287624786230562734672583763984576267 + +'''d''' +'' +"" +"'" +"""efwb +onv +8979 +""" +'0' +'None' + +n = None +l = [] +d = {} + +class C(object): + + def meth(self): + pass + + @classmethod + def cmeth(cls): + pass + + @staticmethod + def smeth(): + pass \ No newline at end of file diff --git a/python/ql/test/library-tests/options b/python/ql/test/library-tests/options new file mode 100644 index 000000000000..72e0053821fa --- /dev/null +++ b/python/ql/test/library-tests/options @@ -0,0 +1,2 @@ +semmle-extractor-options: --max-import-depth=1 +automatic_locations: true diff --git a/python/ql/test/library-tests/parentheses/Parens.expected b/python/ql/test/library-tests/parentheses/Parens.expected new file mode 100644 index 000000000000..c19d295830de --- /dev/null +++ b/python/ql/test/library-tests/parentheses/Parens.expected @@ -0,0 +1,30 @@ +| 26 | a | +| 27 | Attribute | +| 28 | BinaryExpr | +| 29 | Str | +| 30 | Str | +| 31 | List | +| 32 | Dict | +| 33 | Tuple | +| 34 | Subscript | +| 35 | f() | +| 36 | f() | +| 37 | Attribute() | +| 38 | Attribute() | +| 39 | Subscript | +| 40 | Subscript | +| 41 | Yield | +| 42 | ListComp | +| 43 | SetComp | +| 44 | DictComp | +| 45 | Str | +| 46 | BinaryExpr | +| 47 | BinaryExpr | +| 48 | BinaryExpr | +| 49 | BinaryExpr | +| 50 | BinaryExpr | +| 51 | BinaryExpr | +| 52 | BinaryExpr | +| 55 | BinaryExpr | +| 56 | BinaryExpr | +| 61 | Tuple | \ No newline at end of file diff --git a/python/ql/test/library-tests/parentheses/Parens.ql b/python/ql/test/library-tests/parentheses/Parens.ql new file mode 100644 index 000000000000..9006bb9ff521 --- /dev/null +++ b/python/ql/test/library-tests/parentheses/Parens.ql @@ -0,0 +1,5 @@ +import python + +from Expr e +where e.isParenthesized() +select e.getLocation().getStartLine(), e.toString() diff --git a/python/ql/test/library-tests/parentheses/test.py b/python/ql/test/library-tests/parentheses/test.py new file mode 100644 index 000000000000..74fb2824a1e1 --- /dev/null +++ b/python/ql/test/library-tests/parentheses/test.py @@ -0,0 +1,64 @@ +# No +import mod +def no(): + a + a.b + 1+p + b"Hi" + u"Hi" + [x] + {} + x,y + s[9] + f() + f(a,b) + o.m() + o.m(a, b) + o.m[0] + x()[0] + yield v + [1 for a in b] + {x for x in z} + {x:y for x,y in z} + +#Yes +def yes(): + (a) + (a.b) + (1+p) + (b"Hi") + (u"Hi") + ([x]) + ({}) + (x,y) + (s[9]) + (f()) + (f(a,b)) + (o.m()) + (o.m(a, b)) + (o.m[0]) + (x()[0]) + (yield 1) + ([1 for a in b]) + ({x for x in z}) + ({x:y for x,y in z}) + (b"x") + ("x"+1) + (1+f()) + (1+2+f()) + ("Failed to parse template " + name + ": " + repr(ex)) + (1+2+3+4) + (1+2+3+4+5) + (1+2+3+4+5+6) + +def multiline(): + ( 1 + + ( 2 * 3 + * 4 ) + ) + + ( + x, + 1, + "a" + ) diff --git a/python/ql/test/library-tests/regex/Alternation.expected b/python/ql/test/library-tests/regex/Alternation.expected new file mode 100644 index 000000000000..cba6212a273c --- /dev/null +++ b/python/ql/test/library-tests/regex/Alternation.expected @@ -0,0 +1,17 @@ +| (?:(?:\n\r?)\|^)( *)\\S | 3 | 12 | (?:\n\r?)\|^ | 3 | 10 | (?:\n\r?) | +| (?:(?:\n\r?)\|^)( *)\\S | 3 | 12 | (?:\n\r?)\|^ | 11 | 12 | ^ | +| (?:[^%]\|^)?%\\((\\w*)\\)[a-z] | 3 | 9 | [^%]\|^ | 3 | 7 | [^%] | +| (?:[^%]\|^)?%\\((\\w*)\\)[a-z] | 3 | 9 | [^%]\|^ | 8 | 9 | ^ | +| (?P[\\w]+)\| | 0 | 16 | (?P[\\w]+)\| | 0 | 15 | (?P[\\w]+) | +| (?P[\\w]+)\| | 0 | 16 | (?P[\\w]+)\| | 16 | 16 | | +| (\\033\|~{) | 1 | 8 | \\033\|~{ | 1 | 5 | \\033 | +| (\\033\|~{) | 1 | 8 | \\033\|~{ | 6 | 8 | ~{ | +| \\\|\\[\\][123]\|\\{\\} | 0 | 16 | \\\|\\[\\][123]\|\\{\\} | 0 | 11 | \\\|\\[\\][123] | +| \\\|\\[\\][123]\|\\{\\} | 0 | 16 | \\\|\\[\\][123]\|\\{\\} | 12 | 16 | \\{\\} | +| ^(^y\|^z)(u$\|v$)$ | 2 | 7 | ^y\|^z | 2 | 4 | ^y | +| ^(^y\|^z)(u$\|v$)$ | 2 | 7 | ^y\|^z | 5 | 7 | ^z | +| ^(^y\|^z)(u$\|v$)$ | 9 | 14 | u$\|v$ | 9 | 11 | u$ | +| ^(^y\|^z)(u$\|v$)$ | 9 | 14 | u$\|v$ | 12 | 14 | v$ | +| x\|(?[\\w]+)\| | 10 | 12 | +| (?m)^(?!$) | 4 | 5 | +| (?m)^(?!$) | 8 | 9 | +| (\\033\|~{) | 1 | 5 | +| (\\033\|~{) | 6 | 7 | +| (\\033\|~{) | 7 | 8 | +| [\ufffd-\ufffd] | 1 | 2 | +| [\ufffd-\ufffd] | 3 | 4 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | 1 | 2 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | 3 | 4 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | 6 | 7 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | 8 | 9 | +| []] | 1 | 2 | +| [^-] | 2 | 3 | +| [^A-Z] | 2 | 3 | +| [^A-Z] | 4 | 5 | +| [^]] | 2 | 3 | +| \\A[+-]?\\d+ | 0 | 2 | +| \\A[+-]?\\d+ | 3 | 4 | +| \\A[+-]?\\d+ | 4 | 5 | +| \\A[+-]?\\d+ | 7 | 9 | +| \\\|\\[\\][123]\|\\{\\} | 0 | 2 | +| \\\|\\[\\][123]\|\\{\\} | 2 | 4 | +| \\\|\\[\\][123]\|\\{\\} | 4 | 6 | +| \\\|\\[\\][123]\|\\{\\} | 7 | 8 | +| \\\|\\[\\][123]\|\\{\\} | 8 | 9 | +| \\\|\\[\\][123]\|\\{\\} | 9 | 10 | +| \\\|\\[\\][123]\|\\{\\} | 12 | 14 | +| \\\|\\[\\][123]\|\\{\\} | 14 | 16 | +| ^(^y\|^z)(u$\|v$)$ | 0 | 1 | +| ^(^y\|^z)(u$\|v$)$ | 2 | 3 | +| ^(^y\|^z)(u$\|v$)$ | 3 | 4 | +| ^(^y\|^z)(u$\|v$)$ | 5 | 6 | +| ^(^y\|^z)(u$\|v$)$ | 6 | 7 | +| ^(^y\|^z)(u$\|v$)$ | 9 | 10 | +| ^(^y\|^z)(u$\|v$)$ | 10 | 11 | +| ^(^y\|^z)(u$\|v$)$ | 12 | 13 | +| ^(^y\|^z)(u$\|v$)$ | 13 | 14 | +| ^(^y\|^z)(u$\|v$)$ | 15 | 16 | +| ^.$ | 0 | 1 | +| ^.$ | 1 | 2 | +| ^.$ | 2 | 3 | +| ^[A-Z_]+$(?[\\w]+)\| | first | 9 | 13 | +| (?P[\\w]+)\| | first | 9 | 14 | +| (?P[\\w]+)\| | last | 9 | 13 | +| (?P[\\w]+)\| | last | 9 | 14 | +| (?m)^(?!$) | first | 4 | 5 | +| (?m)^(?!$) | first | 8 | 9 | +| (?m)^(?!$) | last | 4 | 5 | +| (?m)^(?!$) | last | 8 | 9 | +| (\\033\|~{) | first | 1 | 5 | +| (\\033\|~{) | first | 6 | 7 | +| (\\033\|~{) | last | 1 | 5 | +| (\\033\|~{) | last | 7 | 8 | +| [\ufffd-\ufffd] | first | 0 | 5 | +| [\ufffd-\ufffd] | last | 0 | 5 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | first | 0 | 5 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | last | 5 | 10 | +| []] | first | 0 | 3 | +| []] | last | 0 | 3 | +| [^-] | first | 0 | 4 | +| [^-] | last | 0 | 4 | +| [^A-Z] | first | 0 | 6 | +| [^A-Z] | last | 0 | 6 | +| [^]] | first | 0 | 4 | +| [^]] | last | 0 | 4 | +| \\A[+-]?\\d+ | first | 0 | 2 | +| \\A[+-]?\\d+ | last | 7 | 9 | +| \\A[+-]?\\d+ | last | 7 | 10 | +| \\\|\\[\\][123]\|\\{\\} | first | 0 | 2 | +| \\\|\\[\\][123]\|\\{\\} | first | 12 | 14 | +| \\\|\\[\\][123]\|\\{\\} | last | 6 | 11 | +| \\\|\\[\\][123]\|\\{\\} | last | 14 | 16 | +| ^(^y\|^z)(u$\|v$)$ | first | 0 | 1 | +| ^(^y\|^z)(u$\|v$)$ | first | 2 | 3 | +| ^(^y\|^z)(u$\|v$)$ | first | 3 | 4 | +| ^(^y\|^z)(u$\|v$)$ | first | 5 | 6 | +| ^(^y\|^z)(u$\|v$)$ | first | 6 | 7 | +| ^(^y\|^z)(u$\|v$)$ | last | 9 | 10 | +| ^(^y\|^z)(u$\|v$)$ | last | 10 | 11 | +| ^(^y\|^z)(u$\|v$)$ | last | 12 | 13 | +| ^(^y\|^z)(u$\|v$)$ | last | 13 | 14 | +| ^(^y\|^z)(u$\|v$)$ | last | 15 | 16 | +| ^.$ | first | 0 | 1 | +| ^.$ | first | 1 | 2 | +| ^.$ | last | 1 | 2 | +| ^.$ | last | 2 | 3 | +| ^[A-Z_]+$(?[\\w]+)\| | 0 | 15 | (?P[\\w]+) | 9 | 14 | [\\w]+ | +| (?m)^(?!$) | 5 | 10 | (?!$) | 8 | 9 | $ | +| (\\033\|~{) | 0 | 9 | (\\033\|~{) | 1 | 8 | \\033\|~{ | +| ^(^y\|^z)(u$\|v$)$ | 1 | 8 | (^y\|^z) | 2 | 7 | ^y\|^z | +| ^(^y\|^z)(u$\|v$)$ | 8 | 15 | (u$\|v$) | 9 | 14 | u$\|v$ | +| ^[A-Z_]+$(?[\\w]+)\| | 9 | 14 | false | +| \\A[+-]?\\d+ | 2 | 7 | true | +| \\A[+-]?\\d+ | 7 | 10 | false | +| ^[A-Z_]+$(?[\\w]+)\| | char | 10 | 12 | +| (?P[\\w]+)\| | char-set | 9 | 13 | +| (?P[\\w]+)\| | choice | 0 | 16 | +| (?P[\\w]+)\| | non-empty group | 0 | 15 | +| (?P[\\w]+)\| | qualified | 9 | 14 | +| (?P[\\w]+)\| | sequence | 0 | 15 | +| (?m)^(?!$) | $ | 8 | 9 | +| (?m)^(?!$) | ^ | 4 | 5 | +| (?m)^(?!$) | empty group | 0 | 4 | +| (?m)^(?!$) | empty group | 5 | 10 | +| (?m)^(?!$) | sequence | 0 | 10 | +| (?m)^(?!$) | sequence | 8 | 9 | +| (\\033\|~{) | char | 1 | 5 | +| (\\033\|~{) | char | 6 | 7 | +| (\\033\|~{) | char | 7 | 8 | +| (\\033\|~{) | choice | 1 | 8 | +| (\\033\|~{) | non-empty group | 0 | 9 | +| (\\033\|~{) | sequence | 0 | 9 | +| (\\033\|~{) | sequence | 1 | 5 | +| (\\033\|~{) | sequence | 6 | 8 | +| [\ufffd-\ufffd] | char | 1 | 2 | +| [\ufffd-\ufffd] | char | 3 | 4 | +| [\ufffd-\ufffd] | char-set | 0 | 5 | +| [\ufffd-\ufffd] | sequence | 0 | 5 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | char | 1 | 2 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | char | 3 | 4 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | char | 6 | 7 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | char | 8 | 9 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | char-set | 0 | 5 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | char-set | 5 | 10 | +| [\ufffd-\ufffd][\ufffd-\ufffd] | sequence | 0 | 10 | +| []] | char | 1 | 2 | +| []] | char-set | 0 | 3 | +| []] | sequence | 0 | 3 | +| [^-] | char | 2 | 3 | +| [^-] | char-set | 0 | 4 | +| [^-] | sequence | 0 | 4 | +| [^A-Z] | char | 2 | 3 | +| [^A-Z] | char | 4 | 5 | +| [^A-Z] | char-set | 0 | 6 | +| [^A-Z] | sequence | 0 | 6 | +| [^]] | char | 2 | 3 | +| [^]] | char-set | 0 | 4 | +| [^]] | sequence | 0 | 4 | +| \\A[+-]?\\d+ | char | 0 | 2 | +| \\A[+-]?\\d+ | char | 3 | 4 | +| \\A[+-]?\\d+ | char | 4 | 5 | +| \\A[+-]?\\d+ | char | 7 | 9 | +| \\A[+-]?\\d+ | char-set | 2 | 6 | +| \\A[+-]?\\d+ | qualified | 2 | 7 | +| \\A[+-]?\\d+ | qualified | 7 | 10 | +| \\A[+-]?\\d+ | sequence | 0 | 10 | +| \\\|\\[\\][123]\|\\{\\} | char | 0 | 2 | +| \\\|\\[\\][123]\|\\{\\} | char | 2 | 4 | +| \\\|\\[\\][123]\|\\{\\} | char | 4 | 6 | +| \\\|\\[\\][123]\|\\{\\} | char | 7 | 8 | +| \\\|\\[\\][123]\|\\{\\} | char | 8 | 9 | +| \\\|\\[\\][123]\|\\{\\} | char | 9 | 10 | +| \\\|\\[\\][123]\|\\{\\} | char | 12 | 14 | +| \\\|\\[\\][123]\|\\{\\} | char | 14 | 16 | +| \\\|\\[\\][123]\|\\{\\} | char-set | 6 | 11 | +| \\\|\\[\\][123]\|\\{\\} | choice | 0 | 16 | +| \\\|\\[\\][123]\|\\{\\} | sequence | 0 | 11 | +| \\\|\\[\\][123]\|\\{\\} | sequence | 12 | 16 | +| ^(^y\|^z)(u$\|v$)$ | $ | 10 | 11 | +| ^(^y\|^z)(u$\|v$)$ | $ | 13 | 14 | +| ^(^y\|^z)(u$\|v$)$ | $ | 15 | 16 | +| ^(^y\|^z)(u$\|v$)$ | ^ | 0 | 1 | +| ^(^y\|^z)(u$\|v$)$ | ^ | 2 | 3 | +| ^(^y\|^z)(u$\|v$)$ | ^ | 5 | 6 | +| ^(^y\|^z)(u$\|v$)$ | char | 3 | 4 | +| ^(^y\|^z)(u$\|v$)$ | char | 6 | 7 | +| ^(^y\|^z)(u$\|v$)$ | char | 9 | 10 | +| ^(^y\|^z)(u$\|v$)$ | char | 12 | 13 | +| ^(^y\|^z)(u$\|v$)$ | choice | 2 | 7 | +| ^(^y\|^z)(u$\|v$)$ | choice | 9 | 14 | +| ^(^y\|^z)(u$\|v$)$ | non-empty group | 1 | 8 | +| ^(^y\|^z)(u$\|v$)$ | non-empty group | 8 | 15 | +| ^(^y\|^z)(u$\|v$)$ | sequence | 0 | 16 | +| ^(^y\|^z)(u$\|v$)$ | sequence | 2 | 4 | +| ^(^y\|^z)(u$\|v$)$ | sequence | 5 | 7 | +| ^(^y\|^z)(u$\|v$)$ | sequence | 9 | 11 | +| ^(^y\|^z)(u$\|v$)$ | sequence | 12 | 14 | +| ^.$ | $ | 2 | 3 | +| ^.$ | . | 1 | 2 | +| ^.$ | ^ | 0 | 1 | +| ^.$ | sequence | 0 | 3 | +| ^[A-Z_]+$(?[\w]+)|') +re.compile(r'\|\[\][123]|\{\}') +re.compile(r'^.$') +re.compile(r'[^A-Z]') +# 0123456789ABCDEF +re.sub('(?m)^(?!$)', indent*' ', s) +re.compile("(?:(?:\n\r?)|^)( *)\S") +re.compile("[]]") +re.compile("[^]]") +re.compile("[^-]") + +#Lookbehind group +re.compile(r'x|(?= 20 and +( + state.appliesTo(f, ctx) and sense = true + or + state.mayNotApplyTo(f, ctx) and sense = false +) + +select f.getLocation().toString(), f, ctx, state, sense diff --git a/python/ql/test/library-tests/state_tracking/Violations.expected b/python/ql/test/library-tests/state_tracking/Violations.expected new file mode 100644 index 000000000000..8deeb3c651fc --- /dev/null +++ b/python/ql/test/library-tests/state_tracking/Violations.expected @@ -0,0 +1,7 @@ +| global.py:23 | ControlFlowNode for frobnicate() | initialized | +| global.py:26 | ControlFlowNode for exacerbate() | frobnicated | +| test.py:42 | ControlFlowNode for frobnicate() | initialized | +| test.py:48 | ControlFlowNode for exacerbate() | frobnicated | +| test.py:51 | ControlFlowNode for exacerbate() | frobnicated | +| test.py:65 | ControlFlowNode for frobnicate() | initialized | +| test.py:75 | ControlFlowNode for exacerbate() | frobnicated | diff --git a/python/ql/test/library-tests/state_tracking/Violations.ql b/python/ql/test/library-tests/state_tracking/Violations.ql new file mode 100644 index 000000000000..8da2a0500efe --- /dev/null +++ b/python/ql/test/library-tests/state_tracking/Violations.ql @@ -0,0 +1,15 @@ + +import python +import Lib + +from ControlFlowNode f, TrackableState state +where +( + callTo(f, "exacerbate") and state = "frobnicated" + or + callTo(f, "frobnicate") and state = "initialized" +) +and +state.mayNotApplyTo(f) + +select f.getLocation().toString(), f.toString(), state.toString() diff --git a/python/ql/test/library-tests/state_tracking/global.py b/python/ql/test/library-tests/state_tracking/global.py new file mode 100644 index 000000000000..5268cae9c9a5 --- /dev/null +++ b/python/ql/test/library-tests/state_tracking/global.py @@ -0,0 +1,34 @@ +# We must initialize before we frobnicate and we must frobnicate before exacerbating. + +from test import initialize, frobnicate, exacerbate + + + + + + + + + + + + + + + + +#To keep test results tidy don't put any thing we want results for before line 20 + +def bad1(): + frobnicate() + +def bad2(): + exacerbate() + +def good(): + exacerbate() + +bad2() +bad1() +initialize() +good() \ No newline at end of file diff --git a/python/ql/test/library-tests/state_tracking/test.py b/python/ql/test/library-tests/state_tracking/test.py new file mode 100644 index 000000000000..227d09f0b421 --- /dev/null +++ b/python/ql/test/library-tests/state_tracking/test.py @@ -0,0 +1,75 @@ +# We must initialize before we frobnicate and we must frobnicate before exacerbating. + +#Keep these functions at low line numbers to keep test results tidy. +def initialize(): + pass + +def frobnicate(): + pass + +def exacerbate(): + pass + +def defrobnicate(): + pass + + + + + +#Actual code of interest after line 20 +def good1(): + initialize() + frobnicate() + exacerbate() + +def good2(test): + if test: + initialize() + if test: + frobnicate() + +def init_and_frob(): + initialize() + frobnicate() + +def good3(): + init_and_frob() + exacerbate() + +def bad1(): + #No init + frobnicate() + exacerbate() + +def bad2(): + initialize() + #No frobnicate + exacerbate() + +def bad3(): + exacerbate() + +def good4(frob): + initialized = False + if frob: + initialize() + initialized = True + if initialized: + frobnicate() + exacerbate() + +def bad4(frob): + if frob: + initialize() + frobnicate() + exacerbate() + +def on_off(): + initialize() + frobnicate() + exacerbate() + defrobnicate() + frobnicate() + defrobnicate() + exacerbate() diff --git a/python/ql/test/library-tests/stmts/general/AstParent.expected b/python/ql/test/library-tests/stmts/general/AstParent.expected new file mode 100644 index 000000000000..f082a67fcf66 --- /dev/null +++ b/python/ql/test/library-tests/stmts/general/AstParent.expected @@ -0,0 +1 @@ +| 0 | diff --git a/python/ql/test/library-tests/stmts/general/AstParent.ql b/python/ql/test/library-tests/stmts/general/AstParent.ql new file mode 100644 index 000000000000..b7ea6f44ac3c --- /dev/null +++ b/python/ql/test/library-tests/stmts/general/AstParent.ql @@ -0,0 +1,8 @@ +import python + +/* The result of this query should always be 0, *regardless* of the database. */ + +select +count(AstNode c | not exists(c.getParentNode()) and not c instanceof Module) ++ +count(AstNode c | strictcount(c.getParentNode()) > 1) diff --git a/python/ql/test/library-tests/stmts/general/SubExpressions.expected b/python/ql/test/library-tests/stmts/general/SubExpressions.expected new file mode 100644 index 000000000000..e40356ab46b1 --- /dev/null +++ b/python/ql/test/library-tests/stmts/general/SubExpressions.expected @@ -0,0 +1,6 @@ +| Delete | Attribute | Attribute | 4 | +| Delete | Attribute | a | 4 | +| Delete | Subscript | Subscript | 2 | +| Delete | Subscript | a | 2 | +| Delete | Subscript | b | 2 | +| Delete | x | x | 3 | \ No newline at end of file diff --git a/python/ql/test/library-tests/stmts/general/SubExpressions.ql b/python/ql/test/library-tests/stmts/general/SubExpressions.ql new file mode 100644 index 000000000000..deaff1e96101 --- /dev/null +++ b/python/ql/test/library-tests/stmts/general/SubExpressions.ql @@ -0,0 +1,5 @@ + +import python + +from Stmt s +select s.toString(), s.getASubExpression().toString(), s.getASubExpression().getASubExpression*().toString(), s.getLocation().getStartLine() \ No newline at end of file diff --git a/python/ql/test/library-tests/stmts/general/subexpr_test.py b/python/ql/test/library-tests/stmts/general/subexpr_test.py new file mode 100644 index 000000000000..14332d142290 --- /dev/null +++ b/python/ql/test/library-tests/stmts/general/subexpr_test.py @@ -0,0 +1,4 @@ + +del a[b] +del x +del a.b diff --git a/python/ql/test/library-tests/stmts/raise_stmt/AST.expected b/python/ql/test/library-tests/stmts/raise_stmt/AST.expected new file mode 100644 index 000000000000..655013b45920 --- /dev/null +++ b/python/ql/test/library-tests/stmts/raise_stmt/AST.expected @@ -0,0 +1,7 @@ +| 0 | Module test | 2 | FunctionDef | +| 2 | Function f | 3 | Raise | +| 2 | Function f | 4 | Raise | +| 2 | FunctionDef | 2 | FunctionExpr | +| 2 | FunctionDef | 2 | f | +| 2 | FunctionExpr | 2 | Function f | +| 4 | Raise | 4 | Exception | \ No newline at end of file diff --git a/python/ql/test/library-tests/stmts/raise_stmt/AST.ql b/python/ql/test/library-tests/stmts/raise_stmt/AST.ql new file mode 100644 index 000000000000..d9daaa8514b7 --- /dev/null +++ b/python/ql/test/library-tests/stmts/raise_stmt/AST.ql @@ -0,0 +1,7 @@ + + +import python + +from AstNode parent, AstNode child +where child.getParentNode() = parent +select parent.getLocation().getStartLine(), parent.toString(), child.getLocation().getStartLine(), child.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/stmts/raise_stmt/test.py b/python/ql/test/library-tests/stmts/raise_stmt/test.py new file mode 100644 index 000000000000..26d38df54a1a --- /dev/null +++ b/python/ql/test/library-tests/stmts/raise_stmt/test.py @@ -0,0 +1,4 @@ + +def f(): + raise + raise Exception diff --git a/python/ql/test/library-tests/stmts/try_stmt/AST.expected b/python/ql/test/library-tests/stmts/try_stmt/AST.expected new file mode 100644 index 000000000000..a8c2778fc999 --- /dev/null +++ b/python/ql/test/library-tests/stmts/try_stmt/AST.expected @@ -0,0 +1,34 @@ +| 0 | Module test | 2 | FunctionDef | +| 2 | Function f | 3 | Try | +| 2 | Function f | 12 | Try | +| 2 | Function f | 17 | Try | +| 2 | FunctionDef | 2 | FunctionExpr | +| 2 | FunctionDef | 2 | f | +| 2 | FunctionExpr | 2 | Function f | +| 3 | Try | 4 | ExprStmt | +| 3 | Try | 5 | Try | +| 3 | Try | 9 | ExceptStmt | +| 4 | ExprStmt | 4 | call() | +| 4 | call() | 4 | call | +| 5 | Try | 6 | ExprStmt | +| 5 | Try | 7 | ExceptStmt | +| 6 | ExprStmt | 6 | nested() | +| 6 | nested() | 6 | nested | +| 7 | ExceptStmt | 7 | Exception | +| 7 | ExceptStmt | 8 | Return | +| 8 | Return | 8 | IntegerLiteral | +| 9 | ExceptStmt | 10 | Return | +| 10 | Return | 10 | IntegerLiteral | +| 12 | Try | 13 | ExprStmt | +| 12 | Try | 14 | ExceptStmt | +| 13 | ExprStmt | 13 | call2() | +| 13 | call2() | 13 | call2 | +| 14 | ExceptStmt | 14 | Exception | +| 14 | ExceptStmt | 15 | Return | +| 15 | Return | 15 | IntegerLiteral | +| 17 | Try | 18 | ExprStmt | +| 17 | Try | 20 | ExprStmt | +| 18 | ExprStmt | 18 | call3a() | +| 18 | call3a() | 18 | call3a | +| 20 | ExprStmt | 20 | call3b() | +| 20 | call3b() | 20 | call3b | \ No newline at end of file diff --git a/python/ql/test/library-tests/stmts/try_stmt/AST.ql b/python/ql/test/library-tests/stmts/try_stmt/AST.ql new file mode 100644 index 000000000000..d9daaa8514b7 --- /dev/null +++ b/python/ql/test/library-tests/stmts/try_stmt/AST.ql @@ -0,0 +1,7 @@ + + +import python + +from AstNode parent, AstNode child +where child.getParentNode() = parent +select parent.getLocation().getStartLine(), parent.toString(), child.getLocation().getStartLine(), child.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/stmts/try_stmt/test.py b/python/ql/test/library-tests/stmts/try_stmt/test.py new file mode 100644 index 000000000000..f7d5b124c6df --- /dev/null +++ b/python/ql/test/library-tests/stmts/try_stmt/test.py @@ -0,0 +1,20 @@ + +def f(): + try: + call() + try: + nested() + except Exception: + return 1 + except: + return 2 + + try: + call2() + except Exception: + return 2 + + try: + call3a() + finally: + call3b() \ No newline at end of file diff --git a/python/ql/test/library-tests/stmts/with_stmt/AST.expected b/python/ql/test/library-tests/stmts/with_stmt/AST.expected new file mode 100644 index 000000000000..acae99eab4c6 --- /dev/null +++ b/python/ql/test/library-tests/stmts/with_stmt/AST.expected @@ -0,0 +1,20 @@ +| 0 | Module test | 2 | FunctionDef | +| 0 | Module test | 6 | FunctionDef | +| 2 | Function f | 3 | With | +| 2 | FunctionDef | 2 | FunctionExpr | +| 2 | FunctionDef | 2 | f | +| 2 | FunctionExpr | 2 | Function f | +| 3 | With | 3 | a | +| 3 | With | 4 | ExprStmt | +| 4 | ExprStmt | 4 | call() | +| 4 | call() | 4 | call | +| 6 | Function g | 7 | With | +| 6 | FunctionDef | 6 | FunctionExpr | +| 6 | FunctionDef | 6 | g | +| 6 | FunctionExpr | 6 | Function g | +| 7 | With | 7 | x | +| 7 | With | 8 | With | +| 8 | With | 8 | y | +| 8 | With | 9 | ExprStmt | +| 9 | ExprStmt | 9 | call() | +| 9 | call() | 9 | call | \ No newline at end of file diff --git a/python/ql/test/library-tests/stmts/with_stmt/AST.ql b/python/ql/test/library-tests/stmts/with_stmt/AST.ql new file mode 100644 index 000000000000..d9daaa8514b7 --- /dev/null +++ b/python/ql/test/library-tests/stmts/with_stmt/AST.ql @@ -0,0 +1,7 @@ + + +import python + +from AstNode parent, AstNode child +where child.getParentNode() = parent +select parent.getLocation().getStartLine(), parent.toString(), child.getLocation().getStartLine(), child.toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/stmts/with_stmt/test.py b/python/ql/test/library-tests/stmts/with_stmt/test.py new file mode 100644 index 000000000000..4a19af00c057 --- /dev/null +++ b/python/ql/test/library-tests/stmts/with_stmt/test.py @@ -0,0 +1,9 @@ + +def f(): + with a: + call() + +def g(): + with x: + with y: + call() \ No newline at end of file diff --git a/python/ql/test/library-tests/taint/exception_traceback/TestNode.expected b/python/ql/test/library-tests/taint/exception_traceback/TestNode.expected new file mode 100644 index 000000000000..b0b2a23b14b0 --- /dev/null +++ b/python/ql/test/library-tests/taint/exception_traceback/TestNode.expected @@ -0,0 +1,27 @@ +| test.py:10:11:10:47 | test.py:10 | MyException() | exception.kind | +| test.py:15:25:15:25 | test.py:15 | e | exception.kind | +| test.py:16:13:16:34 | test.py:16 | Attribute() | exception.info | +| test.py:17:15:17:15 | test.py:17 | s | exception.info | +| test.py:19:13:19:36 | test.py:19 | Attribute() | [exception.info] | +| test.py:20:13:20:37 | test.py:20 | Attribute() | [exception.info] | +| test.py:21:13:21:36 | test.py:21 | Attribute() | [exception.info] | +| test.py:21:35:21:35 | test.py:21 | t | [exception.info] | +| test.py:22:13:22:58 | test.py:22 | Attribute() | [exception.info] | +| test.py:23:13:23:57 | test.py:23 | Attribute() | [exception.info] | +| test.py:24:13:24:35 | test.py:24 | Attribute() | [exception.info] | +| test.py:25:13:25:36 | test.py:25 | Attribute() | [exception.info] | +| test.py:26:25:26:25 | test.py:26 | e | exception.kind | +| test.py:26:25:26:33 | test.py:26 | Attribute | exception.info | +| test.py:26:25:26:41 | test.py:26 | Tuple | [[exception.info]] | +| test.py:26:25:26:41 | test.py:26 | Tuple | [exception.info] | +| test.py:26:36:26:36 | test.py:26 | e | exception.kind | +| test.py:26:36:26:41 | test.py:26 | Attribute | [exception.info] | +| test.py:27:19:27:19 | test.py:27 | t | [exception.info] | +| test.py:27:22:27:22 | test.py:27 | u | [exception.info] | +| test.py:27:25:27:25 | test.py:27 | v | [exception.info] | +| test.py:27:28:27:28 | test.py:27 | w | [exception.info] | +| test.py:27:31:27:31 | test.py:27 | x | [exception.info] | +| test.py:27:34:27:34 | test.py:27 | y | [exception.info] | +| test.py:27:37:27:37 | test.py:27 | z | [exception.info] | +| test.py:27:40:27:46 | test.py:27 | message | exception.info | +| test.py:27:49:27:52 | test.py:27 | args | [exception.info] | diff --git a/python/ql/test/library-tests/taint/exception_traceback/TestNode.ql b/python/ql/test/library-tests/taint/exception_traceback/TestNode.ql new file mode 100644 index 000000000000..b9ec3c2ff3e9 --- /dev/null +++ b/python/ql/test/library-tests/taint/exception_traceback/TestNode.ql @@ -0,0 +1,8 @@ +import python + +import semmle.python.security.Exceptions +import semmle.python.web.HttpResponse + +from TaintedNode node +where not node.getLocation().getFile().inStdlib() +select node.getLocation(), node.getNode().getNode().toString(), node.getTaintKind() \ No newline at end of file diff --git a/python/ql/test/library-tests/taint/exception_traceback/TestSource.expected b/python/ql/test/library-tests/taint/exception_traceback/TestSource.expected new file mode 100644 index 000000000000..e8b2074bc28f --- /dev/null +++ b/python/ql/test/library-tests/taint/exception_traceback/TestSource.expected @@ -0,0 +1,10 @@ +| test.py:10 | MyException() | exception.kind | +| test.py:15 | e | exception.kind | +| test.py:16 | Attribute() | exception.info | +| test.py:19 | Attribute() | [exception.info] | +| test.py:20 | Attribute() | [exception.info] | +| test.py:21 | Attribute() | [exception.info] | +| test.py:22 | Attribute() | [exception.info] | +| test.py:23 | Attribute() | [exception.info] | +| test.py:24 | Attribute() | [exception.info] | +| test.py:25 | Attribute() | [exception.info] | diff --git a/python/ql/test/library-tests/taint/exception_traceback/TestSource.ql b/python/ql/test/library-tests/taint/exception_traceback/TestSource.ql new file mode 100644 index 000000000000..8e625641b77b --- /dev/null +++ b/python/ql/test/library-tests/taint/exception_traceback/TestSource.ql @@ -0,0 +1,11 @@ +import python + +import semmle.python.security.Exceptions +import semmle.python.web.HttpResponse + + +from TaintSource src, TaintKind kind +where + src.isSourceOf(kind) and + not src.getLocation().getFile().inStdlib() +select src.getLocation().toString(), src.(ControlFlowNode).getNode().toString(), kind \ No newline at end of file diff --git a/python/ql/test/library-tests/taint/exception_traceback/TestStep.expected b/python/ql/test/library-tests/taint/exception_traceback/TestStep.expected new file mode 100644 index 000000000000..196c1e92c2ce --- /dev/null +++ b/python/ql/test/library-tests/taint/exception_traceback/TestStep.expected @@ -0,0 +1,16 @@ +| Taint [exception.info] | test.py:19 | Attribute() | | --> | Taint [exception.info] | test.py:21 | t | | +| Taint [exception.info] | test.py:19 | Attribute() | | --> | Taint [exception.info] | test.py:27 | t | | +| Taint [exception.info] | test.py:20 | Attribute() | | --> | Taint [exception.info] | test.py:27 | u | | +| Taint [exception.info] | test.py:21 | Attribute() | | --> | Taint [exception.info] | test.py:27 | v | | +| Taint [exception.info] | test.py:22 | Attribute() | | --> | Taint [exception.info] | test.py:27 | w | | +| Taint [exception.info] | test.py:23 | Attribute() | | --> | Taint [exception.info] | test.py:27 | x | | +| Taint [exception.info] | test.py:24 | Attribute() | | --> | Taint [exception.info] | test.py:27 | y | | +| Taint [exception.info] | test.py:25 | Attribute() | | --> | Taint [exception.info] | test.py:27 | z | | +| Taint [exception.info] | test.py:26 | Attribute | | --> | Taint [[exception.info]] | test.py:26 | Tuple | | +| Taint [exception.info] | test.py:26 | Attribute | | --> | Taint [exception.info] | test.py:27 | args | | +| Taint exception.info | test.py:16 | Attribute() | | --> | Taint exception.info | test.py:17 | s | | +| Taint exception.info | test.py:26 | Attribute | | --> | Taint [exception.info] | test.py:26 | Tuple | | +| Taint exception.info | test.py:26 | Attribute | | --> | Taint exception.info | test.py:27 | message | | +| Taint exception.kind | test.py:15 | e | | --> | Taint exception.kind | test.py:26 | e | | +| Taint exception.kind | test.py:26 | e | | --> | Taint [exception.info] | test.py:26 | Attribute | | +| Taint exception.kind | test.py:26 | e | | --> | Taint exception.info | test.py:26 | Attribute | | diff --git a/python/ql/test/library-tests/taint/exception_traceback/TestStep.ql b/python/ql/test/library-tests/taint/exception_traceback/TestStep.ql new file mode 100644 index 000000000000..227cba3c2935 --- /dev/null +++ b/python/ql/test/library-tests/taint/exception_traceback/TestStep.ql @@ -0,0 +1,14 @@ +import python + +import semmle.python.security.Exceptions +import semmle.python.web.HttpResponse + +from TaintedNode n, TaintedNode s +where + s = n.getASuccessor() and + not n.getLocation().getFile().inStdlib() and + not s.getLocation().getFile().inStdlib() +select + n.getTrackedValue(), n.getLocation().toString(), n.getNode().getNode().toString(), n.getContext(), + " --> ", + s.getTrackedValue(), s.getLocation().toString(), s.getNode().getNode().toString(), s.getContext() diff --git a/python/ql/test/library-tests/taint/exception_traceback/test.py b/python/ql/test/library-tests/taint/exception_traceback/test.py new file mode 100644 index 000000000000..37479bc63db4 --- /dev/null +++ b/python/ql/test/library-tests/taint/exception_traceback/test.py @@ -0,0 +1,30 @@ +from __future__ import print_function + +import traceback +import sys + +class MyException(Exception): + pass + +def raise_secret_exception(): + raise MyException("Message", "secret info") + +def foo(): + try: + raise_secret_exception() + except Exception as e: + s = traceback.format_exc() + print(s) + etype, evalue, tb = sys.exc_info() + t = traceback.extract_tb(tb) + u = traceback.extract_stack() + v = traceback.format_list(t) + w = traceback.format_exception_only(etype, evalue) + x = traceback.format_exception(etype, evalue, tb) + y = traceback.format_tb(tb) + z = traceback.format_stack() + message, args = e.message, e.args + print(tb, t, u, v, w, x, y, z, message, args) + + +foo() diff --git a/python/ql/test/library-tests/taint/extensions/ExtensionsLib.qll b/python/ql/test/library-tests/taint/extensions/ExtensionsLib.qll new file mode 100644 index 000000000000..9cbbc6f9e2d1 --- /dev/null +++ b/python/ql/test/library-tests/taint/extensions/ExtensionsLib.qll @@ -0,0 +1,94 @@ +import python + +import semmle.python.security.TaintTracking + +class SimpleTest extends TaintKind { + + SimpleTest() { + this = "simple.test" + } + +} + +class SimpleSink extends TaintSink { + + override string toString() { result = "Simple sink" } + + SimpleSink() { + exists(CallNode call | + call.getFunction().(NameNode).getId() = "SINK" and + this = call.getAnArg() + ) + } + + override predicate sinks(TaintKind taint) { + taint instanceof SimpleTest + } + +} + +class SimpleSource extends TaintSource { + + SimpleSource() { this.(NameNode).getId() = "SOURCE" } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof SimpleTest + } + + override string toString() { + result = "simple.source" + } + +} + + +predicate visit_call(CallNode call, FunctionObject func) { + exists(AttrNode attr, ClassObject cls, string name | + name.prefix(6) = "visit_" and + func = cls.lookupAttribute(name) and + attr.getObject("visit").refersTo(_, cls, _) and + attr = call.getFunction() + ) +} + +/* Test call extensions by tracking taint through visitor methods */ + +class TestCallReturnExtension extends DataFlowExtension::DataFlowNode { + + TestCallReturnExtension() { + exists(PyFunctionObject func | + visit_call(_, func) and + this = func.getAReturnedNode() + ) + } + + override ControlFlowNode getAReturnSuccessorNode(CallNode call) { + exists(PyFunctionObject func | + visit_call(call, func) and + this = func.getAReturnedNode() and + result = call + ) + } + +} + +class TestCallParameterExtension extends DataFlowExtension::DataFlowNode { + + TestCallParameterExtension() { + exists(PyFunctionObject func, CallNode call | + visit_call(call, func) and + this = call.getAnArg() + ) + } + + override ControlFlowNode getACalleeSuccessorNode(CallNode call) { + exists(PyFunctionObject func | + visit_call(call, func) and + exists(int n | + this = call.getArg(n) and + result.getNode() = func.getFunction().getArg(n+1) + ) + ) + } + +} diff --git a/python/ql/test/library-tests/taint/extensions/TestNode.expected b/python/ql/test/library-tests/taint/extensions/TestNode.expected new file mode 100644 index 000000000000..3c0912519d2d --- /dev/null +++ b/python/ql/test/library-tests/taint/extensions/TestNode.expected @@ -0,0 +1,8 @@ +| Taint simple.test | visitor.py:10 | arg | visitor.py:26 | +| Taint simple.test | visitor.py:13 | arg | visitor.py:26 | +| Taint simple.test | visitor.py:18 | arg | visitor.py:26 | +| Taint simple.test | visitor.py:19 | arg | visitor.py:26 | +| Taint simple.test | visitor.py:21 | arg | visitor.py:26 | +| Taint simple.test | visitor.py:26 | Attribute() | | +| Taint simple.test | visitor.py:26 | SOURCE | | +| Taint simple.test | visitor.py:27 | x | | diff --git a/python/ql/test/library-tests/taint/extensions/TestNode.ql b/python/ql/test/library-tests/taint/extensions/TestNode.ql new file mode 100644 index 000000000000..11f697a8bfed --- /dev/null +++ b/python/ql/test/library-tests/taint/extensions/TestNode.ql @@ -0,0 +1,8 @@ +import python + +import ExtensionsLib + + +from TaintedNode n +select n.getTrackedValue(), n.getLocation().toString(), n.getNode().getNode().toString(), n.getContext() + diff --git a/python/ql/test/library-tests/taint/extensions/TestStep.expected b/python/ql/test/library-tests/taint/extensions/TestStep.expected new file mode 100644 index 000000000000..382aabc3ae72 --- /dev/null +++ b/python/ql/test/library-tests/taint/extensions/TestStep.expected @@ -0,0 +1,7 @@ +| Taint simple.test | visitor.py:10 | arg | visitor.py:26 | --> | Taint simple.test | visitor.py:13 | arg | visitor.py:26 | +| Taint simple.test | visitor.py:18 | arg | visitor.py:26 | --> | Taint simple.test | visitor.py:19 | arg | visitor.py:26 | +| Taint simple.test | visitor.py:19 | arg | visitor.py:26 | --> | Taint simple.test | visitor.py:26 | Attribute() | | +| Taint simple.test | visitor.py:26 | Attribute() | | --> | Taint simple.test | visitor.py:27 | x | | +| Taint simple.test | visitor.py:26 | SOURCE | | --> | Taint simple.test | visitor.py:10 | arg | visitor.py:26 | +| Taint simple.test | visitor.py:26 | SOURCE | | --> | Taint simple.test | visitor.py:18 | arg | visitor.py:26 | +| Taint simple.test | visitor.py:26 | SOURCE | | --> | Taint simple.test | visitor.py:21 | arg | visitor.py:26 | diff --git a/python/ql/test/library-tests/taint/extensions/TestStep.ql b/python/ql/test/library-tests/taint/extensions/TestStep.ql new file mode 100644 index 000000000000..4623101a9573 --- /dev/null +++ b/python/ql/test/library-tests/taint/extensions/TestStep.ql @@ -0,0 +1,11 @@ +import python + +import ExtensionsLib + + +from TaintedNode n, TaintedNode s +where s = n.getASuccessor() +select + n.getTrackedValue(), n.getLocation().toString(), n.getNode().getNode().toString(), n.getContext(), + " --> ", + s.getTrackedValue(), s.getLocation().toString(), s.getNode().getNode().toString(), s.getContext() diff --git a/python/ql/test/library-tests/taint/extensions/visitor.py b/python/ql/test/library-tests/taint/extensions/visitor.py new file mode 100644 index 000000000000..c68867319ef6 --- /dev/null +++ b/python/ql/test/library-tests/taint/extensions/visitor.py @@ -0,0 +1,27 @@ + + +class Visitor(object): + '''Visitor pattern ''' + + def __init__(self, labels): + self.labels = labels + self.priority = 0 + + def visit(self, node, arg): + """Visit a node.""" + method = 'visit_' + node.__class__.__name__ + getattr(self, method, self.generic_visit)(node, arg) + + def generic_visit(self, node, arg): + pass + + def visit_Class(self, node, arg): + return arg + + def visit_Function(self, func, arg): + pass + +v = Visitor() + +x = v.visit(dont_care, SOURCE) +x diff --git a/python/ql/test/library-tests/taint/general/Contexts.expected b/python/ql/test/library-tests/taint/general/Contexts.expected new file mode 100644 index 000000000000..e78a0d94abd0 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/Contexts.expected @@ -0,0 +1,31 @@ +| carrier.py:17 | Function __init__ | +| carrier.py:25 | Function __init__ | +| carrier.py:25 | Function hub | +| carrier.py:29 | Function hub | +| carrier.py:33 | Function __init__ | +| deep.py:6 from deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | Function f1 | +| deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | Function f2 | +| deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | Function f3 | +| deep.py:15 from deep.py:18 from deep.py:20 | Function f4 | +| deep.py:18 from deep.py:20 | Function f5 | +| deep.py:20 | Function f6 | +| rockpaperscissors.py:13 | Function rock | +| rockpaperscissors.py:16 | Function paper | +| rockpaperscissors.py:21 | Function scissors | +| rockpaperscissors.py:26 | Function scissors | +| rockpaperscissors.py:31 | Function paper | +| rockpaperscissors.py:32 | Function paper | +| sanitizer.py:10 | Function isEscapedSql | +| sanitizer.py:17 | Function isValidCommand | +| test.py:21 | Function sink | +| test.py:25 | Function sink | +| test.py:47 from test.py:55 | Function sink | +| test.py:51 from test.py:63 | Function sink | +| test.py:51 from test.py:70 | Function sink | +| test.py:55 | Function sink2 | +| test.py:63 | Function sink3 | +| test.py:70 | Function sink3 | +| test.py:77 | Function hub | +| test.py:116 | Function hub | +| test.py:117 | Function x_sink | +| test.py:121 | Function hub | diff --git a/python/ql/test/library-tests/taint/general/Contexts.ql b/python/ql/test/library-tests/taint/general/Contexts.ql new file mode 100644 index 000000000000..c0a63c5f228d --- /dev/null +++ b/python/ql/test/library-tests/taint/general/Contexts.ql @@ -0,0 +1,9 @@ + +import python +import semmle.python.security.TaintTest +import TaintLib + +from CallContext context, Scope s +where exists(CallContext caller | caller.getCallee(_) = context) and context.appliesToScope(s) +select context, s.toString() + diff --git a/python/ql/test/library-tests/taint/general/ModuleAttribute.expected b/python/ql/test/library-tests/taint/general/ModuleAttribute.expected new file mode 100644 index 000000000000..b13f01db429a --- /dev/null +++ b/python/ql/test/library-tests/taint/general/ModuleAttribute.expected @@ -0,0 +1,4 @@ +| Module deep | x | Taint simple.test | | deep.py:20 | +| Module module | dangerous | Taint simple.test | | module.py:3 | +| Module test | module | Attribute 'dangerous' taint simple.test | | test.py:85 | +| Module test | unsafe | Taint simple.test | | test.py:155 | diff --git a/python/ql/test/library-tests/taint/general/ModuleAttribute.ql b/python/ql/test/library-tests/taint/general/ModuleAttribute.ql new file mode 100644 index 000000000000..6daca6cda1bf --- /dev/null +++ b/python/ql/test/library-tests/taint/general/ModuleAttribute.ql @@ -0,0 +1,10 @@ +import python +import semmle.python.security.TaintTest +import TaintLib + + +from ModuleObject m, string name, TaintedNode origin + +where TaintFlowTest::module_attribute_tainted(m, name, origin) + +select m.toString(), name, origin.getTrackedValue(), origin.getContext(), origin.getLocation().toString() diff --git a/python/ql/test/library-tests/taint/general/ParamSource.expected b/python/ql/test/library-tests/taint/general/ParamSource.expected new file mode 100644 index 000000000000..d085cd7d178c --- /dev/null +++ b/python/ql/test/library-tests/taint/general/ParamSource.expected @@ -0,0 +1,6 @@ +| test | carrier.py:4 | 18 | Attribute | test | +| test | test.py:12 | 13 | arg | test | +| test | test.py:46 | 13 | arg | test | +| test | test.py:49 | 13 | arg | test | +| test | test.py:72 | 78 | t | test | +| test | test.py:72 | 83 | t | test | diff --git a/python/ql/test/library-tests/taint/general/ParamSource.ql b/python/ql/test/library-tests/taint/general/ParamSource.ql new file mode 100644 index 000000000000..664fd8b77e5a --- /dev/null +++ b/python/ql/test/library-tests/taint/general/ParamSource.ql @@ -0,0 +1,53 @@ +import python +import semmle.python.security.TaintTracking + +/* Standard library sink */ +import semmle.python.security.injection.Command + +class TestKind extends TaintKind { + TestKind() { + this = "test" + } + +} + +class CustomSource extends TaintSource { + + CustomSource() { + exists(Parameter p | + p.asName().getId() = "arg" and + this.(ControlFlowNode).getNode() = p + ) + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof TestKind + } + + override string toString() { + result = "Source of untrusted input" + } + +} + +class SimpleSink extends TaintSink { + + override string toString() { result = "Simple sink" } + + SimpleSink() { + exists(CallNode call | + call.getFunction().(NameNode).getId() = "SINK" and + this = call.getAnArg() + ) + } + + override predicate sinks(TaintKind taint) { + taint instanceof TestKind + } + +} + +from TaintSource src, TaintSink sink, TaintKind srckind, TaintKind sinkkind + +where src.flowsToSink(srckind, sink) and sink.sinks(sinkkind) +select srckind, src.getLocation().toString(), sink.getLocation().getStartLine(), sink.(ControlFlowNode).getNode().toString(), sinkkind diff --git a/python/ql/test/library-tests/taint/general/TaintLib.qll b/python/ql/test/library-tests/taint/general/TaintLib.qll new file mode 100644 index 000000000000..6eae51f8c48f --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TaintLib.qll @@ -0,0 +1,336 @@ +import python +import semmle.python.security.TaintTracking + + +class SimpleTest extends TaintKind { + + SimpleTest() { + this = "simple.test" + } + +} + +class SimpleSink extends TaintSink { + + override string toString() { result = "Simple sink" } + + SimpleSink() { + exists(CallNode call | + call.getFunction().(NameNode).getId() = "SINK" and + this = call.getAnArg() + ) + } + + override predicate sinks(TaintKind taint) { + taint instanceof SimpleTest + } + +} + +class SimpleSource extends TaintSource { + + SimpleSource() { this.(NameNode).getId() = "SOURCE" } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof SimpleTest + } + + override string toString() { + result = "simple.source" + } + +} + +class SimpleSanitizer extends Sanitizer { + + SimpleSanitizer() { this = "Simple sanitizer" } + + override predicate sanitizingNode(TaintKind taint, ControlFlowNode node) { + node.(CallNode).getFunction().(NameNode).getId() = "SANITIZE" and + taint instanceof SimpleTest + } + +} + +class BasicCustomTaint extends TaintKind { + + BasicCustomTaint() { + this = "basic.custom" + } + + override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { + tonode.(CallNode).getAnArg() = fromnode and + tonode.(CallNode).getFunction().(NameNode).getId() = "TAINT_FROM_ARG" and + result = this + } + +} + +class BasicCustomSink extends TaintSink { + + override string toString() { result = "Basic custom sink" } + + BasicCustomSink() { + exists(CallNode call | + call.getFunction().(NameNode).getId() = "CUSTOM_SINK" and + this = call.getAnArg() + ) + } + + override predicate sinks(TaintKind taint) { + taint instanceof BasicCustomTaint + } + +} + + +class BasicCustomSource extends TaintSource { + + BasicCustomSource() { this.(NameNode).getId() = "CUSTOM_SOURCE" } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof BasicCustomTaint + } + + override string toString() { + result = "basic.custom.source" + } + +} + +class Rock extends TaintKind { + + Rock() { this = "rock" } + + override TaintKind getTaintOfMethodResult(string name) { + name = "prev" and result instanceof Scissors + } + + predicate isSink(ControlFlowNode sink) { + exists(CallNode call | + call.getArg(0) = sink and + call.getFunction().(NameNode).getId() = "paper" + ) + } + +} + +class Paper extends TaintKind { + + Paper() { this = "paper" } + + override TaintKind getTaintOfMethodResult(string name) { + name = "prev" and result instanceof Rock + } + + predicate isSink(ControlFlowNode sink) { + exists(CallNode call | + call.getArg(0) = sink and + call.getFunction().(NameNode).getId() = "scissors" + ) + } + +} + +class Scissors extends TaintKind { + + Scissors() { this = "scissors" } + + override TaintKind getTaintOfMethodResult(string name) { + name = "prev" and result instanceof Paper + } + + predicate isSink(ControlFlowNode sink) { + exists(CallNode call | + call.getArg(0) = sink and + call.getFunction().(NameNode).getId() = "rock" + ) + } + +} + +class RockPaperScissorSource extends TaintSource { + + RockPaperScissorSource() { + exists(string name | + this.(NameNode).getId() = name | + name = "ROCK" or name = "PAPER" or name = "SCISSORS" + ) + } + + override predicate isSourceOf(TaintKind kind) { + kind = this.(NameNode).getId().toLowerCase() + } + + override string toString() { + result = "rock.paper.scissors.source" + } + +} + +private predicate function_param(string funcname, ControlFlowNode arg) { + exists(FunctionObject f | + f.getName() = funcname and + arg = f.getArgumentForCall(_, _) + ) +} + +class RockPaperScissorSink extends TaintSink { + + RockPaperScissorSink() { + exists(string name | + function_param(name, this) | + name = "rock" or name = "paper" or name = "scissors" + ) + } + + override predicate sinks(TaintKind taint) { + exists(string name | + function_param(name, this) | + name = "paper" and taint = "rock" + or + name = "rock" and taint = "scissors" + or + name = "scissors" and taint = "paper" + ) + } + + override string toString() { + result = "rock.paper.scissors.sink" + } + +} + +class TaintCarrier extends TaintKind { + + TaintCarrier() { this = "explicit.carrier" } + + override TaintKind getTaintOfMethodResult(string name) { + name = "get_taint" and result instanceof SimpleTest + } + + +} + +/* There is no sink for `TaintCarrier`. It is not "dangerous" in itself; it merely holds a `SimpleTest`. */ +class TaintCarrierSource extends TaintSource { + + TaintCarrierSource() { + this.(NameNode).getId() = "TAINT_CARRIER_SOURCE" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof TaintCarrier + } + + override string toString() { + result = "taint.carrier.source" + } +} + + +/* Some more realistic examples */ + +abstract class UserInput extends TaintKind { + + bindingset[this] + UserInput() { any() } + +} + +class UserInputSource extends TaintSource { + + UserInputSource() { + this.(CallNode).getFunction().(NameNode).getId() = "user_input" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof UserInput + } + + override string toString() { + result = "user.input.source" + } + +} + +class SqlInjectionTaint extends UserInput { + + SqlInjectionTaint() { this = "SQL injection" } + +} + +class CommandInjectionTaint extends UserInput { + + CommandInjectionTaint() { this = "Command injection" } + +} + +class SqlSanitizer extends Sanitizer { + + SqlSanitizer() { this = "SQL sanitizer" } + + /** Holds if `test` shows value to be untainted with `taint` */ + override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { + exists(FunctionObject f, CallNode call | + f.getName() = "isEscapedSql" and + test.getTest() = call and + call.getAnArg() = test.getSourceVariable().getAUse() and + f.getACall() = call and + test.getSense() = true + ) and + taint instanceof SqlInjectionTaint + } + +} + +class CommandSanitizer extends Sanitizer { + + CommandSanitizer() { this = "Command sanitizer" } + + /** Holds if `test` shows value to be untainted with `taint` */ + override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { + exists(FunctionObject f | + f.getName() = "isValidCommand" and + f.getACall().(CallNode).getAnArg() = test.getSourceVariable().getAUse() and + test.getSense() = true + ) and + taint instanceof CommandInjectionTaint + } + +} + +class SqlQuery extends TaintSink { + + SqlQuery() { + exists(CallNode call | + call.getFunction().(NameNode).getId() = "sql_query" and + call.getAnArg() = this + ) + } + + override string toString() { result = "SQL query" } + + override predicate sinks(TaintKind taint) { + taint instanceof SqlInjectionTaint + } + +} + + +class OsCommand extends TaintSink { + + OsCommand() { + exists(CallNode call | + call.getFunction().(NameNode).getId() = "os_command" and + call.getAnArg() = this + ) + } + + override string toString() { result = "OS command" } + + override predicate sinks(TaintKind taint) { + taint instanceof CommandInjectionTaint + } + +} diff --git a/python/ql/test/library-tests/taint/general/TaintSanity.expected b/python/ql/test/library-tests/taint/general/TaintSanity.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/taint/general/TaintSanity.ql b/python/ql/test/library-tests/taint/general/TaintSanity.ql new file mode 100644 index 000000000000..394c39ce4917 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TaintSanity.ql @@ -0,0 +1,26 @@ +import python +import semmle.python.security.TaintTest +import TaintLib + +from TaintFlowTest::TrackedValue taint, CallContext c, ControlFlowNode n, string what +where +not exists(TaintedNode t | t.getTrackedValue() = taint and t.getNode() = n and t.getContext() = c) and +( + TaintFlowTest::step(_, taint, c, n) and what = "missing node at end of step" + or + n.(TaintSource).isSourceOf(taint.(TaintFlowTest::TrackedTaint).getKind(), c) and what = "missing node for source" + +) +or +exists(TaintedNode t | t.getTrackedValue() = taint and t.getNode() = n and t.getContext() = c + | + not TaintFlowTest::step(_, taint, c, n) and + not n.(TaintSource).isSourceOf(taint.(TaintFlowTest::TrackedTaint).getKind(), c) and what = "TaintedNode with no reason" + or + TaintFlowTest::step(t, taint, c, n) and what = "step ends where it starts" + or + TaintFlowTest::step(t, _, _, _) and not TaintFlowTest::step(_, taint, c, n) and + not n.(TaintSource).isSourceOf(taint.(TaintFlowTest::TrackedTaint).getKind(), c) and what = "No predecessor and not a source" +) + +select n.getLocation(), taint, c, n.toString(), what diff --git a/python/ql/test/library-tests/taint/general/TestNode.expected b/python/ql/test/library-tests/taint/general/TestNode.expected new file mode 100644 index 000000000000..c7ae50289a07 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestNode.expected @@ -0,0 +1,222 @@ +| Attribute 'attr' taint explicit.carrier | carrier.py:5 | self | carrier.py:33 | +| Attribute 'attr' taint explicit.carrier | carrier.py:33 | ImplicitCarrier() | | +| Attribute 'attr' taint explicit.carrier | carrier.py:34 | c | | +| Attribute 'attr' taint simple.test | carrier.py:5 | self | carrier.py:17 | +| Attribute 'attr' taint simple.test | carrier.py:5 | self | carrier.py:25 | +| Attribute 'attr' taint simple.test | carrier.py:13 | arg | carrier.py:25 | +| Attribute 'attr' taint simple.test | carrier.py:14 | arg | carrier.py:25 | +| Attribute 'attr' taint simple.test | carrier.py:17 | ImplicitCarrier() | | +| Attribute 'attr' taint simple.test | carrier.py:18 | c | | +| Attribute 'attr' taint simple.test | carrier.py:25 | ImplicitCarrier() | | +| Attribute 'attr' taint simple.test | carrier.py:25 | hub() | | +| Attribute 'attr' taint simple.test | carrier.py:26 | c | | +| Attribute 'dangerous' taint simple.test | test.py:85 | ImportExpr | | +| Attribute 'dangerous' taint simple.test | test.py:88 | module | | +| Attribute 'dangerous' taint simple.test | test.py:92 | module | | +| Attribute 'dangerous' taint simple.test | test.py:96 | module | | +| Attribute 'dangerous' taint simple.test | test.py:100 | module | | +| Attribute 'dangerous' taint simple.test | test.py:110 | module | | +| Attribute 'dangerous' taint simple.test | test.py:115 | module | | +| Attribute 'dangerous' taint simple.test | test.py:155 | ImportExpr | | +| Attribute 'x' taint simple.test | test.py:72 | arg | test.py:116 | +| Attribute 'x' taint simple.test | test.py:73 | arg | test.py:116 | +| Attribute 'x' taint simple.test | test.py:105 | arg | test.py:117 | +| Attribute 'x' taint simple.test | test.py:106 | arg | test.py:117 | +| Attribute 'x' taint simple.test | test.py:110 | t | | +| Attribute 'x' taint simple.test | test.py:111 | t | | +| Attribute 'x' taint simple.test | test.py:115 | t | | +| Attribute 'x' taint simple.test | test.py:116 | hub() | | +| Attribute 'x' taint simple.test | test.py:116 | t | | +| Attribute 'x' taint simple.test | test.py:117 | t | | +| Taint Command injection | sanitizer.py:3 | arg | sanitizer.py:10 | +| Taint Command injection | sanitizer.py:5 | arg | sanitizer.py:17 | +| Taint Command injection | sanitizer.py:9 | user_input() | | +| Taint Command injection | sanitizer.py:10 | x | | +| Taint Command injection | sanitizer.py:11 | x | | +| Taint Command injection | sanitizer.py:13 | x | | +| Taint Command injection | sanitizer.py:16 | user_input() | | +| Taint Command injection | sanitizer.py:17 | x | | +| Taint Command injection | sanitizer.py:20 | x | | +| Taint Command injection | sanitizer.py:24 | user_input() | | +| Taint Command injection | sanitizer.py:25 | x | | +| Taint Command injection | sanitizer.py:26 | x | | +| Taint Command injection | sanitizer.py:28 | x | | +| Taint Command injection | sanitizer.py:31 | user_input() | | +| Taint Command injection | sanitizer.py:32 | x | | +| Taint Command injection | sanitizer.py:33 | x | | +| Taint Command injection | sanitizer.py:35 | x | | +| Taint SQL injection | sanitizer.py:3 | arg | sanitizer.py:10 | +| Taint SQL injection | sanitizer.py:5 | arg | sanitizer.py:17 | +| Taint SQL injection | sanitizer.py:9 | user_input() | | +| Taint SQL injection | sanitizer.py:10 | x | | +| Taint SQL injection | sanitizer.py:13 | x | | +| Taint SQL injection | sanitizer.py:16 | user_input() | | +| Taint SQL injection | sanitizer.py:17 | x | | +| Taint SQL injection | sanitizer.py:18 | x | | +| Taint SQL injection | sanitizer.py:20 | x | | +| Taint SQL injection | sanitizer.py:24 | user_input() | | +| Taint SQL injection | sanitizer.py:25 | x | | +| Taint SQL injection | sanitizer.py:26 | x | | +| Taint SQL injection | sanitizer.py:28 | x | | +| Taint SQL injection | sanitizer.py:31 | user_input() | | +| Taint SQL injection | sanitizer.py:32 | x | | +| Taint SQL injection | sanitizer.py:33 | x | | +| Taint SQL injection | sanitizer.py:35 | x | | +| Taint [simple.test] | test.py:168 | List | | +| Taint [simple.test] | test.py:170 | l | | +| Taint [simple.test] | test.py:172 | x | | +| Taint [simple.test] | test.py:174 | l | | +| Taint [simple.test] | test.py:174 | list() | | +| Taint basic.custom | test.py:72 | arg | test.py:121 | +| Taint basic.custom | test.py:73 | arg | test.py:121 | +| Taint basic.custom | test.py:120 | CUSTOM_SOURCE | | +| Taint basic.custom | test.py:121 | TAINT_FROM_ARG() | | +| Taint basic.custom | test.py:121 | hub() | | +| Taint basic.custom | test.py:121 | t | | +| Taint basic.custom | test.py:122 | t | | +| Taint basic.custom | test.py:126 | CUSTOM_SOURCE | | +| Taint basic.custom | test.py:130 | t | | +| Taint basic.custom | test.py:136 | CUSTOM_SOURCE | | +| Taint basic.custom | test.py:142 | t | | +| Taint basic.custom | test.py:146 | CUSTOM_SOURCE | | +| Taint basic.custom | test.py:149 | TAINT_FROM_ARG() | | +| Taint basic.custom | test.py:149 | t | | +| Taint basic.custom | test.py:151 | t | | +| Taint explicit.carrier | carrier.py:4 | arg | carrier.py:33 | +| Taint explicit.carrier | carrier.py:5 | arg | carrier.py:33 | +| Taint explicit.carrier | carrier.py:13 | arg | carrier.py:29 | +| Taint explicit.carrier | carrier.py:14 | arg | carrier.py:29 | +| Taint explicit.carrier | carrier.py:21 | TAINT_CARRIER_SOURCE | | +| Taint explicit.carrier | carrier.py:22 | c | | +| Taint explicit.carrier | carrier.py:29 | TAINT_CARRIER_SOURCE | | +| Taint explicit.carrier | carrier.py:29 | hub() | | +| Taint explicit.carrier | carrier.py:30 | c | | +| Taint explicit.carrier | carrier.py:33 | TAINT_CARRIER_SOURCE | | +| Taint explicit.carrier | carrier.py:34 | Attribute | | +| Taint explicit.carrier | carrier.py:35 | x | | +| Taint paper | rockpaperscissors.py:6 | arg | rockpaperscissors.py:32 | +| Taint paper | rockpaperscissors.py:9 | arg | rockpaperscissors.py:26 | +| Taint paper | rockpaperscissors.py:25 | Attribute() | | +| Taint paper | rockpaperscissors.py:26 | y | | +| Taint paper | rockpaperscissors.py:30 | Attribute() | | +| Taint paper | rockpaperscissors.py:32 | y | | +| Taint rock | rockpaperscissors.py:6 | arg | rockpaperscissors.py:16 | +| Taint rock | rockpaperscissors.py:16 | ROCK | | +| Taint rock | rockpaperscissors.py:19 | ROCK | | +| Taint rock | rockpaperscissors.py:20 | x | | +| Taint rock | rockpaperscissors.py:24 | ROCK | | +| Taint rock | rockpaperscissors.py:25 | x | | +| Taint scissors | rockpaperscissors.py:3 | arg | rockpaperscissors.py:13 | +| Taint scissors | rockpaperscissors.py:6 | arg | rockpaperscissors.py:31 | +| Taint scissors | rockpaperscissors.py:9 | arg | rockpaperscissors.py:21 | +| Taint scissors | rockpaperscissors.py:13 | SCISSORS | | +| Taint scissors | rockpaperscissors.py:20 | Attribute() | | +| Taint scissors | rockpaperscissors.py:21 | y | | +| Taint scissors | rockpaperscissors.py:25 | Attribute() | | +| Taint scissors | rockpaperscissors.py:29 | SCISSORS | | +| Taint scissors | rockpaperscissors.py:30 | x | | +| Taint scissors | rockpaperscissors.py:31 | x | | +| Taint simple.test | carrier.py:4 | arg | carrier.py:17 | +| Taint simple.test | carrier.py:4 | arg | carrier.py:25 | +| Taint simple.test | carrier.py:5 | arg | carrier.py:17 | +| Taint simple.test | carrier.py:5 | arg | carrier.py:25 | +| Taint simple.test | carrier.py:17 | SOURCE | | +| Taint simple.test | carrier.py:18 | Attribute | | +| Taint simple.test | carrier.py:22 | Attribute() | | +| Taint simple.test | carrier.py:25 | SOURCE | | +| Taint simple.test | carrier.py:30 | Attribute() | | +| Taint simple.test | carrier.py:35 | Attribute() | | +| Taint simple.test | deep.py:2 | arg | deep.py:6 from deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:3 | arg | deep.py:6 from deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:5 | arg | deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:6 | arg | deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:6 | f1() | deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:8 | arg | deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:9 | arg | deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:9 | f2() | deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:11 | arg | deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:12 | arg | deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:12 | f3() | deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:14 | arg | deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:15 | arg | deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:15 | f4() | deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:17 | arg | deep.py:20 | +| Taint simple.test | deep.py:18 | arg | deep.py:20 | +| Taint simple.test | deep.py:18 | f5() | deep.py:20 | +| Taint simple.test | deep.py:20 | SOURCE | | +| Taint simple.test | deep.py:20 | f6() | | +| Taint simple.test | deep.py:22 | x | | +| Taint simple.test | module.py:3 | SOURCE | | +| Taint simple.test | module.py:7 | SOURCE | | +| Taint simple.test | module.py:10 | SOURCE | | +| Taint simple.test | test.py:3 | SOURCE | | +| Taint simple.test | test.py:6 | SOURCE | | +| Taint simple.test | test.py:7 | s | | +| Taint simple.test | test.py:10 | SOURCE | | +| Taint simple.test | test.py:12 | arg | test.py:21 | +| Taint simple.test | test.py:12 | arg | test.py:25 | +| Taint simple.test | test.py:12 | arg | test.py:47 from test.py:55 | +| Taint simple.test | test.py:12 | arg | test.py:51 from test.py:63 | +| Taint simple.test | test.py:12 | arg | test.py:51 from test.py:70 | +| Taint simple.test | test.py:13 | arg | test.py:21 | +| Taint simple.test | test.py:13 | arg | test.py:25 | +| Taint simple.test | test.py:13 | arg | test.py:47 from test.py:55 | +| Taint simple.test | test.py:13 | arg | test.py:51 from test.py:63 | +| Taint simple.test | test.py:13 | arg | test.py:51 from test.py:70 | +| Taint simple.test | test.py:16 | source() | | +| Taint simple.test | test.py:17 | t | | +| Taint simple.test | test.py:20 | SOURCE | | +| Taint simple.test | test.py:21 | t | | +| Taint simple.test | test.py:24 | source() | | +| Taint simple.test | test.py:25 | t | | +| Taint simple.test | test.py:31 | SOURCE | | +| Taint simple.test | test.py:37 | SOURCE | | +| Taint simple.test | test.py:41 | t | | +| Taint simple.test | test.py:44 | source() | | +| Taint simple.test | test.py:46 | arg | test.py:55 | +| Taint simple.test | test.py:47 | arg | test.py:55 | +| Taint simple.test | test.py:49 | arg | test.py:63 | +| Taint simple.test | test.py:49 | arg | test.py:70 | +| Taint simple.test | test.py:51 | arg | test.py:63 | +| Taint simple.test | test.py:51 | arg | test.py:70 | +| Taint simple.test | test.py:54 | source2() | | +| Taint simple.test | test.py:55 | t | | +| Taint simple.test | test.py:62 | SOURCE | | +| Taint simple.test | test.py:63 | t | | +| Taint simple.test | test.py:67 | SOURCE | | +| Taint simple.test | test.py:70 | t | | +| Taint simple.test | test.py:72 | arg | test.py:77 | +| Taint simple.test | test.py:73 | arg | test.py:77 | +| Taint simple.test | test.py:76 | SOURCE | | +| Taint simple.test | test.py:77 | hub() | | +| Taint simple.test | test.py:77 | t | | +| Taint simple.test | test.py:78 | t | | +| Taint simple.test | test.py:88 | Attribute | | +| Taint simple.test | test.py:89 | t | | +| Taint simple.test | test.py:100 | Attribute() | | +| Taint simple.test | test.py:101 | t | | +| Taint simple.test | test.py:106 | Attribute | test.py:117 | +| Taint simple.test | test.py:110 | Attribute | | +| Taint simple.test | test.py:111 | Attribute | | +| Taint simple.test | test.py:115 | Attribute | | +| Taint simple.test | test.py:128 | SOURCE | | +| Taint simple.test | test.py:132 | t | | +| Taint simple.test | test.py:138 | SOURCE | | +| Taint simple.test | test.py:140 | t | | +| Taint simple.test | test.py:148 | SOURCE | | +| Taint simple.test | test.py:149 | t | | +| Taint simple.test | test.py:155 | ImportMember | | +| Taint simple.test | test.py:156 | unsafe | | +| Taint simple.test | test.py:159 | SOURCE | | +| Taint simple.test | test.py:160 | t | | +| Taint simple.test | test.py:163 | SOURCE | | +| Taint simple.test | test.py:164 | s | | +| Taint simple.test | test.py:168 | SOURCE | | +| Taint simple.test | test.py:169 | SOURCE | | +| Taint simple.test | test.py:172 | Subscript | | +| Taint simple.test | test.py:173 | Subscript | | +| Taint {simple.test} | test.py:169 | Dict | | +| Taint {simple.test} | test.py:171 | d | | +| Taint {simple.test} | test.py:173 | y | | +| Taint {simple.test} | test.py:175 | d | | +| Taint {simple.test} | test.py:175 | dict() | | diff --git a/python/ql/test/library-tests/taint/general/TestNode.ql b/python/ql/test/library-tests/taint/general/TestNode.ql new file mode 100644 index 000000000000..967d70e51c11 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestNode.ql @@ -0,0 +1,8 @@ +import python +import semmle.python.security.TaintTracking +import TaintLib + + +from TaintedNode n +select n.getTrackedValue(), n.getLocation().toString(), n.getNode().getNode().toString(), n.getContext() + diff --git a/python/ql/test/library-tests/taint/general/TestSanitizers.expected b/python/ql/test/library-tests/taint/general/TestSanitizers.expected new file mode 100644 index 000000000000..2e5b0e4a75ed --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestSanitizers.expected @@ -0,0 +1,2 @@ +| Command sanitizer | Command injection | sanitizer.py:18 | Pi(x_1) [true] | +| SQL sanitizer | SQL injection | sanitizer.py:11 | Pi(x_1) [true] | diff --git a/python/ql/test/library-tests/taint/general/TestSanitizers.ql b/python/ql/test/library-tests/taint/general/TestSanitizers.ql new file mode 100644 index 000000000000..3dca04d581e5 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestSanitizers.ql @@ -0,0 +1,10 @@ + +import python +import semmle.python.security.TaintTracking +import TaintLib + +from Sanitizer s, TaintKind taint, PyEdgeRefinement test +where s.sanitizingEdge(taint, test) +select s, taint, test.getLocation().toString(), test.getRepresentation() + + diff --git a/python/ql/test/library-tests/taint/general/TestSink.expected b/python/ql/test/library-tests/taint/general/TestSink.expected new file mode 100644 index 000000000000..dd169d78f033 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestSink.expected @@ -0,0 +1,34 @@ +| Command injection | sanitizer.py:16 | 20 | x | Command injection | +| Command injection | sanitizer.py:31 | 33 | x | Command injection | +| Command injection | sanitizer.py:31 | 35 | x | Command injection | +| SQL injection | sanitizer.py:9 | 13 | x | SQL injection | +| SQL injection | sanitizer.py:24 | 26 | x | SQL injection | +| SQL injection | sanitizer.py:24 | 28 | x | SQL injection | +| basic.custom | test.py:120 | 122 | t | basic.custom | +| basic.custom | test.py:126 | 130 | t | basic.custom | +| basic.custom | test.py:146 | 151 | t | basic.custom | +| explicit.carrier | carrier.py:21 | 22 | Attribute() | simple.test | +| explicit.carrier | carrier.py:29 | 30 | Attribute() | simple.test | +| explicit.carrier | carrier.py:33 | 35 | Attribute() | simple.test | +| rock | rockpaperscissors.py:16 | 16 | ROCK | rock | +| rock | rockpaperscissors.py:24 | 26 | y | paper | +| scissors | rockpaperscissors.py:13 | 13 | SCISSORS | scissors | +| simple.test | carrier.py:17 | 18 | Attribute | simple.test | +| simple.test | module.py:3 | 89 | t | simple.test | +| simple.test | module.py:3 | 106 | Attribute | simple.test | +| simple.test | module.py:3 | 111 | Attribute | simple.test | +| simple.test | module.py:3 | 156 | unsafe | simple.test | +| simple.test | module.py:7 | 101 | t | simple.test | +| simple.test | test.py:3 | 3 | SOURCE | simple.test | +| simple.test | test.py:6 | 7 | s | simple.test | +| simple.test | test.py:10 | 13 | arg | simple.test | +| simple.test | test.py:10 | 17 | t | simple.test | +| simple.test | test.py:20 | 13 | arg | simple.test | +| simple.test | test.py:37 | 41 | t | simple.test | +| simple.test | test.py:62 | 13 | arg | simple.test | +| simple.test | test.py:67 | 13 | arg | simple.test | +| simple.test | test.py:76 | 78 | t | simple.test | +| simple.test | test.py:128 | 132 | t | simple.test | +| simple.test | test.py:159 | 160 | t | simple.test | +| simple.test | test.py:168 | 172 | Subscript | simple.test | +| simple.test | test.py:169 | 173 | Subscript | simple.test | diff --git a/python/ql/test/library-tests/taint/general/TestSink.ql b/python/ql/test/library-tests/taint/general/TestSink.ql new file mode 100644 index 000000000000..d0361cc204a6 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestSink.ql @@ -0,0 +1,8 @@ +import python +import semmle.python.security.TaintTracking +import TaintLib + +from TaintSource src, TaintSink sink, TaintKind srckind, TaintKind sinkkind + +where src.flowsToSink(srckind, sink) and sink.sinks(sinkkind) +select srckind, src.getLocation().toString(), sink.getLocation().getStartLine(), sink.(ControlFlowNode).getNode().toString(), sinkkind diff --git a/python/ql/test/library-tests/taint/general/TestSource.expected b/python/ql/test/library-tests/taint/general/TestSource.expected new file mode 100644 index 000000000000..ea9012b7735b --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestSource.expected @@ -0,0 +1,42 @@ +| carrier.py:17 | SOURCE | simple.test | +| carrier.py:21 | TAINT_CARRIER_SOURCE | explicit.carrier | +| carrier.py:25 | SOURCE | simple.test | +| carrier.py:29 | TAINT_CARRIER_SOURCE | explicit.carrier | +| carrier.py:33 | TAINT_CARRIER_SOURCE | explicit.carrier | +| deep.py:20 | SOURCE | simple.test | +| module.py:3 | SOURCE | simple.test | +| module.py:7 | SOURCE | simple.test | +| module.py:10 | SOURCE | simple.test | +| rockpaperscissors.py:13 | SCISSORS | scissors | +| rockpaperscissors.py:16 | ROCK | rock | +| rockpaperscissors.py:19 | ROCK | rock | +| rockpaperscissors.py:24 | ROCK | rock | +| rockpaperscissors.py:29 | SCISSORS | scissors | +| sanitizer.py:9 | user_input() | Command injection | +| sanitizer.py:9 | user_input() | SQL injection | +| sanitizer.py:16 | user_input() | Command injection | +| sanitizer.py:16 | user_input() | SQL injection | +| sanitizer.py:24 | user_input() | Command injection | +| sanitizer.py:24 | user_input() | SQL injection | +| sanitizer.py:31 | user_input() | Command injection | +| sanitizer.py:31 | user_input() | SQL injection | +| test.py:3 | SOURCE | simple.test | +| test.py:6 | SOURCE | simple.test | +| test.py:10 | SOURCE | simple.test | +| test.py:20 | SOURCE | simple.test | +| test.py:31 | SOURCE | simple.test | +| test.py:37 | SOURCE | simple.test | +| test.py:62 | SOURCE | simple.test | +| test.py:67 | SOURCE | simple.test | +| test.py:76 | SOURCE | simple.test | +| test.py:120 | CUSTOM_SOURCE | basic.custom | +| test.py:126 | CUSTOM_SOURCE | basic.custom | +| test.py:128 | SOURCE | simple.test | +| test.py:136 | CUSTOM_SOURCE | basic.custom | +| test.py:138 | SOURCE | simple.test | +| test.py:146 | CUSTOM_SOURCE | basic.custom | +| test.py:148 | SOURCE | simple.test | +| test.py:159 | SOURCE | simple.test | +| test.py:163 | SOURCE | simple.test | +| test.py:168 | SOURCE | simple.test | +| test.py:169 | SOURCE | simple.test | diff --git a/python/ql/test/library-tests/taint/general/TestSource.ql b/python/ql/test/library-tests/taint/general/TestSource.ql new file mode 100644 index 000000000000..ba064220bfba --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestSource.ql @@ -0,0 +1,8 @@ +import python +import semmle.python.security.TaintTracking +import TaintLib + + +from TaintSource src, TaintKind kind +where src.isSourceOf(kind) +select src.getLocation().toString(), src.(ControlFlowNode).getNode().toString(), kind diff --git a/python/ql/test/library-tests/taint/general/TestStep.expected b/python/ql/test/library-tests/taint/general/TestStep.expected new file mode 100644 index 000000000000..a4c72fd6b0ce --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestStep.expected @@ -0,0 +1,180 @@ +| Attribute 'attr' taint explicit.carrier | carrier.py:5 | self | carrier.py:33 | --> | Attribute 'attr' taint explicit.carrier | carrier.py:33 | ImplicitCarrier() | | +| Attribute 'attr' taint explicit.carrier | carrier.py:33 | ImplicitCarrier() | | --> | Attribute 'attr' taint explicit.carrier | carrier.py:34 | c | | +| Attribute 'attr' taint explicit.carrier | carrier.py:34 | c | | --> | Taint explicit.carrier | carrier.py:34 | Attribute | | +| Attribute 'attr' taint simple.test | carrier.py:5 | self | carrier.py:17 | --> | Attribute 'attr' taint simple.test | carrier.py:17 | ImplicitCarrier() | | +| Attribute 'attr' taint simple.test | carrier.py:5 | self | carrier.py:25 | --> | Attribute 'attr' taint simple.test | carrier.py:25 | ImplicitCarrier() | | +| Attribute 'attr' taint simple.test | carrier.py:13 | arg | carrier.py:25 | --> | Attribute 'attr' taint simple.test | carrier.py:14 | arg | carrier.py:25 | +| Attribute 'attr' taint simple.test | carrier.py:14 | arg | carrier.py:25 | --> | Attribute 'attr' taint simple.test | carrier.py:25 | hub() | | +| Attribute 'attr' taint simple.test | carrier.py:17 | ImplicitCarrier() | | --> | Attribute 'attr' taint simple.test | carrier.py:18 | c | | +| Attribute 'attr' taint simple.test | carrier.py:18 | c | | --> | Taint simple.test | carrier.py:18 | Attribute | | +| Attribute 'attr' taint simple.test | carrier.py:25 | ImplicitCarrier() | | --> | Attribute 'attr' taint simple.test | carrier.py:13 | arg | carrier.py:25 | +| Attribute 'attr' taint simple.test | carrier.py:25 | hub() | | --> | Attribute 'attr' taint simple.test | carrier.py:26 | c | | +| Attribute 'dangerous' taint simple.test | test.py:85 | ImportExpr | | --> | Attribute 'dangerous' taint simple.test | test.py:88 | module | | +| Attribute 'dangerous' taint simple.test | test.py:85 | ImportExpr | | --> | Attribute 'dangerous' taint simple.test | test.py:92 | module | | +| Attribute 'dangerous' taint simple.test | test.py:85 | ImportExpr | | --> | Attribute 'dangerous' taint simple.test | test.py:96 | module | | +| Attribute 'dangerous' taint simple.test | test.py:85 | ImportExpr | | --> | Attribute 'dangerous' taint simple.test | test.py:100 | module | | +| Attribute 'dangerous' taint simple.test | test.py:85 | ImportExpr | | --> | Attribute 'dangerous' taint simple.test | test.py:110 | module | | +| Attribute 'dangerous' taint simple.test | test.py:85 | ImportExpr | | --> | Attribute 'dangerous' taint simple.test | test.py:115 | module | | +| Attribute 'dangerous' taint simple.test | test.py:88 | module | | --> | Taint simple.test | test.py:88 | Attribute | | +| Attribute 'dangerous' taint simple.test | test.py:110 | module | | --> | Taint simple.test | test.py:110 | Attribute | | +| Attribute 'dangerous' taint simple.test | test.py:115 | module | | --> | Taint simple.test | test.py:115 | Attribute | | +| Attribute 'x' taint simple.test | test.py:72 | arg | test.py:116 | --> | Attribute 'x' taint simple.test | test.py:73 | arg | test.py:116 | +| Attribute 'x' taint simple.test | test.py:73 | arg | test.py:116 | --> | Attribute 'x' taint simple.test | test.py:116 | hub() | | +| Attribute 'x' taint simple.test | test.py:105 | arg | test.py:117 | --> | Attribute 'x' taint simple.test | test.py:106 | arg | test.py:117 | +| Attribute 'x' taint simple.test | test.py:106 | arg | test.py:117 | --> | Taint simple.test | test.py:106 | Attribute | test.py:117 | +| Attribute 'x' taint simple.test | test.py:110 | t | | --> | Attribute 'x' taint simple.test | test.py:111 | t | | +| Attribute 'x' taint simple.test | test.py:111 | t | | --> | Taint simple.test | test.py:111 | Attribute | | +| Attribute 'x' taint simple.test | test.py:115 | t | | --> | Attribute 'x' taint simple.test | test.py:116 | t | | +| Attribute 'x' taint simple.test | test.py:116 | hub() | | --> | Attribute 'x' taint simple.test | test.py:117 | t | | +| Attribute 'x' taint simple.test | test.py:116 | t | | --> | Attribute 'x' taint simple.test | test.py:72 | arg | test.py:116 | +| Attribute 'x' taint simple.test | test.py:117 | t | | --> | Attribute 'x' taint simple.test | test.py:105 | arg | test.py:117 | +| Taint Command injection | sanitizer.py:9 | user_input() | | --> | Taint Command injection | sanitizer.py:10 | x | | +| Taint Command injection | sanitizer.py:9 | user_input() | | --> | Taint Command injection | sanitizer.py:11 | x | | +| Taint Command injection | sanitizer.py:9 | user_input() | | --> | Taint Command injection | sanitizer.py:13 | x | | +| Taint Command injection | sanitizer.py:10 | x | | --> | Taint Command injection | sanitizer.py:3 | arg | sanitizer.py:10 | +| Taint Command injection | sanitizer.py:16 | user_input() | | --> | Taint Command injection | sanitizer.py:17 | x | | +| Taint Command injection | sanitizer.py:16 | user_input() | | --> | Taint Command injection | sanitizer.py:20 | x | | +| Taint Command injection | sanitizer.py:17 | x | | --> | Taint Command injection | sanitizer.py:5 | arg | sanitizer.py:17 | +| Taint Command injection | sanitizer.py:24 | user_input() | | --> | Taint Command injection | sanitizer.py:25 | x | | +| Taint Command injection | sanitizer.py:24 | user_input() | | --> | Taint Command injection | sanitizer.py:26 | x | | +| Taint Command injection | sanitizer.py:24 | user_input() | | --> | Taint Command injection | sanitizer.py:28 | x | | +| Taint Command injection | sanitizer.py:31 | user_input() | | --> | Taint Command injection | sanitizer.py:32 | x | | +| Taint Command injection | sanitizer.py:31 | user_input() | | --> | Taint Command injection | sanitizer.py:33 | x | | +| Taint Command injection | sanitizer.py:31 | user_input() | | --> | Taint Command injection | sanitizer.py:35 | x | | +| Taint SQL injection | sanitizer.py:9 | user_input() | | --> | Taint SQL injection | sanitizer.py:10 | x | | +| Taint SQL injection | sanitizer.py:9 | user_input() | | --> | Taint SQL injection | sanitizer.py:13 | x | | +| Taint SQL injection | sanitizer.py:10 | x | | --> | Taint SQL injection | sanitizer.py:3 | arg | sanitizer.py:10 | +| Taint SQL injection | sanitizer.py:16 | user_input() | | --> | Taint SQL injection | sanitizer.py:17 | x | | +| Taint SQL injection | sanitizer.py:16 | user_input() | | --> | Taint SQL injection | sanitizer.py:18 | x | | +| Taint SQL injection | sanitizer.py:16 | user_input() | | --> | Taint SQL injection | sanitizer.py:20 | x | | +| Taint SQL injection | sanitizer.py:17 | x | | --> | Taint SQL injection | sanitizer.py:5 | arg | sanitizer.py:17 | +| Taint SQL injection | sanitizer.py:24 | user_input() | | --> | Taint SQL injection | sanitizer.py:25 | x | | +| Taint SQL injection | sanitizer.py:24 | user_input() | | --> | Taint SQL injection | sanitizer.py:26 | x | | +| Taint SQL injection | sanitizer.py:24 | user_input() | | --> | Taint SQL injection | sanitizer.py:28 | x | | +| Taint SQL injection | sanitizer.py:31 | user_input() | | --> | Taint SQL injection | sanitizer.py:32 | x | | +| Taint SQL injection | sanitizer.py:31 | user_input() | | --> | Taint SQL injection | sanitizer.py:33 | x | | +| Taint SQL injection | sanitizer.py:31 | user_input() | | --> | Taint SQL injection | sanitizer.py:35 | x | | +| Taint [simple.test] | test.py:168 | List | | --> | Taint [simple.test] | test.py:170 | l | | +| Taint [simple.test] | test.py:168 | List | | --> | Taint [simple.test] | test.py:174 | l | | +| Taint [simple.test] | test.py:170 | l | | --> | Taint [simple.test] | test.py:172 | x | | +| Taint [simple.test] | test.py:172 | x | | --> | Taint simple.test | test.py:172 | Subscript | | +| Taint [simple.test] | test.py:174 | l | | --> | Taint [simple.test] | test.py:174 | list() | | +| Taint basic.custom | test.py:72 | arg | test.py:121 | --> | Taint basic.custom | test.py:73 | arg | test.py:121 | +| Taint basic.custom | test.py:73 | arg | test.py:121 | --> | Taint basic.custom | test.py:121 | hub() | | +| Taint basic.custom | test.py:120 | CUSTOM_SOURCE | | --> | Taint basic.custom | test.py:121 | t | | +| Taint basic.custom | test.py:121 | TAINT_FROM_ARG() | | --> | Taint basic.custom | test.py:72 | arg | test.py:121 | +| Taint basic.custom | test.py:121 | hub() | | --> | Taint basic.custom | test.py:122 | t | | +| Taint basic.custom | test.py:121 | t | | --> | Taint basic.custom | test.py:121 | TAINT_FROM_ARG() | | +| Taint basic.custom | test.py:126 | CUSTOM_SOURCE | | --> | Taint basic.custom | test.py:130 | t | | +| Taint basic.custom | test.py:136 | CUSTOM_SOURCE | | --> | Taint basic.custom | test.py:142 | t | | +| Taint basic.custom | test.py:146 | CUSTOM_SOURCE | | --> | Taint basic.custom | test.py:149 | t | | +| Taint basic.custom | test.py:149 | TAINT_FROM_ARG() | | --> | Taint basic.custom | test.py:151 | t | | +| Taint basic.custom | test.py:149 | t | | --> | Taint basic.custom | test.py:149 | TAINT_FROM_ARG() | | +| Taint explicit.carrier | carrier.py:4 | arg | carrier.py:33 | --> | Taint explicit.carrier | carrier.py:5 | arg | carrier.py:33 | +| Taint explicit.carrier | carrier.py:5 | arg | carrier.py:33 | --> | Attribute 'attr' taint explicit.carrier | carrier.py:5 | self | carrier.py:33 | +| Taint explicit.carrier | carrier.py:13 | arg | carrier.py:29 | --> | Taint explicit.carrier | carrier.py:14 | arg | carrier.py:29 | +| Taint explicit.carrier | carrier.py:14 | arg | carrier.py:29 | --> | Taint explicit.carrier | carrier.py:29 | hub() | | +| Taint explicit.carrier | carrier.py:21 | TAINT_CARRIER_SOURCE | | --> | Taint explicit.carrier | carrier.py:22 | c | | +| Taint explicit.carrier | carrier.py:22 | c | | --> | Taint simple.test | carrier.py:22 | Attribute() | | +| Taint explicit.carrier | carrier.py:29 | TAINT_CARRIER_SOURCE | | --> | Taint explicit.carrier | carrier.py:13 | arg | carrier.py:29 | +| Taint explicit.carrier | carrier.py:29 | hub() | | --> | Taint explicit.carrier | carrier.py:30 | c | | +| Taint explicit.carrier | carrier.py:30 | c | | --> | Taint simple.test | carrier.py:30 | Attribute() | | +| Taint explicit.carrier | carrier.py:33 | TAINT_CARRIER_SOURCE | | --> | Taint explicit.carrier | carrier.py:4 | arg | carrier.py:33 | +| Taint explicit.carrier | carrier.py:34 | Attribute | | --> | Taint explicit.carrier | carrier.py:35 | x | | +| Taint explicit.carrier | carrier.py:35 | x | | --> | Taint simple.test | carrier.py:35 | Attribute() | | +| Taint paper | rockpaperscissors.py:25 | Attribute() | | --> | Taint paper | rockpaperscissors.py:26 | y | | +| Taint paper | rockpaperscissors.py:26 | y | | --> | Taint paper | rockpaperscissors.py:9 | arg | rockpaperscissors.py:26 | +| Taint paper | rockpaperscissors.py:30 | Attribute() | | --> | Taint paper | rockpaperscissors.py:32 | y | | +| Taint paper | rockpaperscissors.py:32 | y | | --> | Taint paper | rockpaperscissors.py:6 | arg | rockpaperscissors.py:32 | +| Taint rock | rockpaperscissors.py:16 | ROCK | | --> | Taint rock | rockpaperscissors.py:6 | arg | rockpaperscissors.py:16 | +| Taint rock | rockpaperscissors.py:19 | ROCK | | --> | Taint rock | rockpaperscissors.py:20 | x | | +| Taint rock | rockpaperscissors.py:20 | x | | --> | Taint scissors | rockpaperscissors.py:20 | Attribute() | | +| Taint rock | rockpaperscissors.py:24 | ROCK | | --> | Taint rock | rockpaperscissors.py:25 | x | | +| Taint rock | rockpaperscissors.py:25 | x | | --> | Taint scissors | rockpaperscissors.py:25 | Attribute() | | +| Taint scissors | rockpaperscissors.py:13 | SCISSORS | | --> | Taint scissors | rockpaperscissors.py:3 | arg | rockpaperscissors.py:13 | +| Taint scissors | rockpaperscissors.py:20 | Attribute() | | --> | Taint scissors | rockpaperscissors.py:21 | y | | +| Taint scissors | rockpaperscissors.py:21 | y | | --> | Taint scissors | rockpaperscissors.py:9 | arg | rockpaperscissors.py:21 | +| Taint scissors | rockpaperscissors.py:25 | Attribute() | | --> | Taint paper | rockpaperscissors.py:25 | Attribute() | | +| Taint scissors | rockpaperscissors.py:29 | SCISSORS | | --> | Taint scissors | rockpaperscissors.py:30 | x | | +| Taint scissors | rockpaperscissors.py:29 | SCISSORS | | --> | Taint scissors | rockpaperscissors.py:31 | x | | +| Taint scissors | rockpaperscissors.py:30 | x | | --> | Taint paper | rockpaperscissors.py:30 | Attribute() | | +| Taint scissors | rockpaperscissors.py:31 | x | | --> | Taint scissors | rockpaperscissors.py:6 | arg | rockpaperscissors.py:31 | +| Taint simple.test | carrier.py:4 | arg | carrier.py:17 | --> | Taint simple.test | carrier.py:5 | arg | carrier.py:17 | +| Taint simple.test | carrier.py:4 | arg | carrier.py:25 | --> | Taint simple.test | carrier.py:5 | arg | carrier.py:25 | +| Taint simple.test | carrier.py:5 | arg | carrier.py:17 | --> | Attribute 'attr' taint simple.test | carrier.py:5 | self | carrier.py:17 | +| Taint simple.test | carrier.py:5 | arg | carrier.py:25 | --> | Attribute 'attr' taint simple.test | carrier.py:5 | self | carrier.py:25 | +| Taint simple.test | carrier.py:17 | SOURCE | | --> | Taint simple.test | carrier.py:4 | arg | carrier.py:17 | +| Taint simple.test | carrier.py:25 | SOURCE | | --> | Taint simple.test | carrier.py:4 | arg | carrier.py:25 | +| Taint simple.test | deep.py:2 | arg | deep.py:6 from deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:3 | arg | deep.py:6 from deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:3 | arg | deep.py:6 from deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:6 | f1() | deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:5 | arg | deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:6 | arg | deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:6 | arg | deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:2 | arg | deep.py:6 from deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:6 | f1() | deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:9 | f2() | deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:8 | arg | deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:9 | arg | deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:9 | arg | deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:5 | arg | deep.py:9 from deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:9 | f2() | deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:12 | f3() | deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:11 | arg | deep.py:15 from deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:12 | arg | deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:12 | arg | deep.py:15 from deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:8 | arg | deep.py:12 from deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:12 | f3() | deep.py:15 from deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:15 | f4() | deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:14 | arg | deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:15 | arg | deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:15 | arg | deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:11 | arg | deep.py:15 from deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:15 | f4() | deep.py:18 from deep.py:20 | --> | Taint simple.test | deep.py:18 | f5() | deep.py:20 | +| Taint simple.test | deep.py:17 | arg | deep.py:20 | --> | Taint simple.test | deep.py:18 | arg | deep.py:20 | +| Taint simple.test | deep.py:18 | arg | deep.py:20 | --> | Taint simple.test | deep.py:14 | arg | deep.py:18 from deep.py:20 | +| Taint simple.test | deep.py:18 | f5() | deep.py:20 | --> | Taint simple.test | deep.py:20 | f6() | | +| Taint simple.test | deep.py:20 | SOURCE | | --> | Taint simple.test | deep.py:17 | arg | deep.py:20 | +| Taint simple.test | deep.py:20 | f6() | | --> | Taint simple.test | deep.py:22 | x | | +| Taint simple.test | module.py:3 | SOURCE | | --> | Attribute 'dangerous' taint simple.test | test.py:85 | ImportExpr | | +| Taint simple.test | module.py:3 | SOURCE | | --> | Attribute 'dangerous' taint simple.test | test.py:155 | ImportExpr | | +| Taint simple.test | module.py:3 | SOURCE | | --> | Taint simple.test | test.py:155 | ImportMember | | +| Taint simple.test | module.py:7 | SOURCE | | --> | Taint simple.test | test.py:100 | Attribute() | | +| Taint simple.test | test.py:6 | SOURCE | | --> | Taint simple.test | test.py:7 | s | | +| Taint simple.test | test.py:10 | SOURCE | | --> | Taint simple.test | test.py:16 | source() | | +| Taint simple.test | test.py:10 | SOURCE | | --> | Taint simple.test | test.py:24 | source() | | +| Taint simple.test | test.py:10 | SOURCE | | --> | Taint simple.test | test.py:44 | source() | | +| Taint simple.test | test.py:12 | arg | test.py:21 | --> | Taint simple.test | test.py:13 | arg | test.py:21 | +| Taint simple.test | test.py:12 | arg | test.py:25 | --> | Taint simple.test | test.py:13 | arg | test.py:25 | +| Taint simple.test | test.py:12 | arg | test.py:47 from test.py:55 | --> | Taint simple.test | test.py:13 | arg | test.py:47 from test.py:55 | +| Taint simple.test | test.py:12 | arg | test.py:51 from test.py:63 | --> | Taint simple.test | test.py:13 | arg | test.py:51 from test.py:63 | +| Taint simple.test | test.py:12 | arg | test.py:51 from test.py:70 | --> | Taint simple.test | test.py:13 | arg | test.py:51 from test.py:70 | +| Taint simple.test | test.py:16 | source() | | --> | Taint simple.test | test.py:17 | t | | +| Taint simple.test | test.py:20 | SOURCE | | --> | Taint simple.test | test.py:21 | t | | +| Taint simple.test | test.py:21 | t | | --> | Taint simple.test | test.py:12 | arg | test.py:21 | +| Taint simple.test | test.py:24 | source() | | --> | Taint simple.test | test.py:25 | t | | +| Taint simple.test | test.py:25 | t | | --> | Taint simple.test | test.py:12 | arg | test.py:25 | +| Taint simple.test | test.py:37 | SOURCE | | --> | Taint simple.test | test.py:41 | t | | +| Taint simple.test | test.py:44 | source() | | --> | Taint simple.test | test.py:54 | source2() | | +| Taint simple.test | test.py:46 | arg | test.py:55 | --> | Taint simple.test | test.py:47 | arg | test.py:55 | +| Taint simple.test | test.py:47 | arg | test.py:55 | --> | Taint simple.test | test.py:12 | arg | test.py:47 from test.py:55 | +| Taint simple.test | test.py:49 | arg | test.py:63 | --> | Taint simple.test | test.py:51 | arg | test.py:63 | +| Taint simple.test | test.py:49 | arg | test.py:70 | --> | Taint simple.test | test.py:51 | arg | test.py:70 | +| Taint simple.test | test.py:51 | arg | test.py:63 | --> | Taint simple.test | test.py:12 | arg | test.py:51 from test.py:63 | +| Taint simple.test | test.py:51 | arg | test.py:70 | --> | Taint simple.test | test.py:12 | arg | test.py:51 from test.py:70 | +| Taint simple.test | test.py:54 | source2() | | --> | Taint simple.test | test.py:55 | t | | +| Taint simple.test | test.py:55 | t | | --> | Taint simple.test | test.py:46 | arg | test.py:55 | +| Taint simple.test | test.py:62 | SOURCE | | --> | Taint simple.test | test.py:63 | t | | +| Taint simple.test | test.py:63 | t | | --> | Taint simple.test | test.py:49 | arg | test.py:63 | +| Taint simple.test | test.py:67 | SOURCE | | --> | Taint simple.test | test.py:70 | t | | +| Taint simple.test | test.py:70 | t | | --> | Taint simple.test | test.py:49 | arg | test.py:70 | +| Taint simple.test | test.py:72 | arg | test.py:77 | --> | Taint simple.test | test.py:73 | arg | test.py:77 | +| Taint simple.test | test.py:73 | arg | test.py:77 | --> | Taint simple.test | test.py:77 | hub() | | +| Taint simple.test | test.py:76 | SOURCE | | --> | Taint simple.test | test.py:77 | t | | +| Taint simple.test | test.py:77 | hub() | | --> | Taint simple.test | test.py:78 | t | | +| Taint simple.test | test.py:77 | t | | --> | Taint simple.test | test.py:72 | arg | test.py:77 | +| Taint simple.test | test.py:88 | Attribute | | --> | Taint simple.test | test.py:89 | t | | +| Taint simple.test | test.py:100 | Attribute() | | --> | Taint simple.test | test.py:101 | t | | +| Taint simple.test | test.py:110 | Attribute | | --> | Attribute 'x' taint simple.test | test.py:110 | t | | +| Taint simple.test | test.py:115 | Attribute | | --> | Attribute 'x' taint simple.test | test.py:115 | t | | +| Taint simple.test | test.py:128 | SOURCE | | --> | Taint simple.test | test.py:132 | t | | +| Taint simple.test | test.py:138 | SOURCE | | --> | Taint simple.test | test.py:140 | t | | +| Taint simple.test | test.py:148 | SOURCE | | --> | Taint simple.test | test.py:149 | t | | +| Taint simple.test | test.py:155 | ImportMember | | --> | Taint simple.test | test.py:156 | unsafe | | +| Taint simple.test | test.py:159 | SOURCE | | --> | Taint simple.test | test.py:160 | t | | +| Taint simple.test | test.py:163 | SOURCE | | --> | Taint simple.test | test.py:164 | s | | +| Taint simple.test | test.py:168 | SOURCE | | --> | Taint [simple.test] | test.py:168 | List | | +| Taint simple.test | test.py:169 | SOURCE | | --> | Taint {simple.test} | test.py:169 | Dict | | +| Taint {simple.test} | test.py:169 | Dict | | --> | Taint {simple.test} | test.py:171 | d | | +| Taint {simple.test} | test.py:169 | Dict | | --> | Taint {simple.test} | test.py:175 | d | | +| Taint {simple.test} | test.py:171 | d | | --> | Taint {simple.test} | test.py:173 | y | | +| Taint {simple.test} | test.py:173 | y | | --> | Taint simple.test | test.py:173 | Subscript | | +| Taint {simple.test} | test.py:175 | d | | --> | Taint {simple.test} | test.py:175 | dict() | | diff --git a/python/ql/test/library-tests/taint/general/TestStep.ql b/python/ql/test/library-tests/taint/general/TestStep.ql new file mode 100644 index 000000000000..191ab6b14831 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestStep.ql @@ -0,0 +1,11 @@ +import python +import semmle.python.security.TaintTracking +import TaintLib + + +from TaintedNode n, TaintedNode s +where s = n.getASuccessor() +select + n.getTrackedValue(), n.getLocation().toString(), n.getNode().getNode().toString(), n.getContext(), + " --> ", + s.getTrackedValue(), s.getLocation().toString(), s.getNode().getNode().toString(), s.getContext() diff --git a/python/ql/test/library-tests/taint/general/TestVar.expected b/python/ql/test/library-tests/taint/general/TestVar.expected new file mode 100644 index 000000000000..5aad11fb5476 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestVar.expected @@ -0,0 +1,179 @@ +| carrier.py:4 | arg_0 | carrier.py:4 | Taint explicit.carrier | arg | +| carrier.py:4 | arg_0 | carrier.py:4 | Taint simple.test | arg | +| carrier.py:5 | self_1 | carrier.py:5 | Attribute 'attr' taint explicit.carrier | self | +| carrier.py:5 | self_1 | carrier.py:5 | Attribute 'attr' taint simple.test | self | +| carrier.py:13 | arg_0 | carrier.py:13 | Attribute 'attr' taint simple.test | arg | +| carrier.py:13 | arg_0 | carrier.py:13 | Taint explicit.carrier | arg | +| carrier.py:17 | c_0 | carrier.py:17 | Attribute 'attr' taint simple.test | ImplicitCarrier() | +| carrier.py:21 | c_0 | carrier.py:21 | Taint explicit.carrier | TAINT_CARRIER_SOURCE | +| carrier.py:22 | c_1 | carrier.py:21 | Taint explicit.carrier | TAINT_CARRIER_SOURCE | +| carrier.py:25 | c_0 | carrier.py:25 | Attribute 'attr' taint simple.test | hub() | +| carrier.py:29 | c_0 | carrier.py:29 | Taint explicit.carrier | hub() | +| carrier.py:30 | c_1 | carrier.py:29 | Taint explicit.carrier | hub() | +| carrier.py:33 | c_0 | carrier.py:33 | Attribute 'attr' taint explicit.carrier | ImplicitCarrier() | +| carrier.py:34 | x_0 | carrier.py:34 | Taint explicit.carrier | Attribute | +| carrier.py:35 | x_1 | carrier.py:34 | Taint explicit.carrier | Attribute | +| deep.py:2 | arg_0 | deep.py:2 | Taint simple.test | arg | +| deep.py:5 | arg_0 | deep.py:5 | Taint simple.test | arg | +| deep.py:6 | arg_1 | deep.py:5 | Taint simple.test | arg | +| deep.py:8 | arg_0 | deep.py:8 | Taint simple.test | arg | +| deep.py:9 | arg_1 | deep.py:8 | Taint simple.test | arg | +| deep.py:11 | arg_0 | deep.py:11 | Taint simple.test | arg | +| deep.py:12 | arg_1 | deep.py:11 | Taint simple.test | arg | +| deep.py:14 | arg_0 | deep.py:14 | Taint simple.test | arg | +| deep.py:15 | arg_1 | deep.py:14 | Taint simple.test | arg | +| deep.py:17 | arg_0 | deep.py:17 | Taint simple.test | arg | +| deep.py:18 | arg_1 | deep.py:17 | Taint simple.test | arg | +| deep.py:20 | x_1 | deep.py:20 | Taint simple.test | f6() | +| module.py:3 | dangerous_0 | module.py:3 | Taint simple.test | SOURCE | +| rockpaperscissors.py:3 | arg_0 | rockpaperscissors.py:3 | Taint scissors | arg | +| rockpaperscissors.py:6 | arg_0 | rockpaperscissors.py:6 | Taint paper | arg | +| rockpaperscissors.py:6 | arg_0 | rockpaperscissors.py:6 | Taint rock | arg | +| rockpaperscissors.py:6 | arg_0 | rockpaperscissors.py:6 | Taint scissors | arg | +| rockpaperscissors.py:9 | arg_0 | rockpaperscissors.py:9 | Taint paper | arg | +| rockpaperscissors.py:9 | arg_0 | rockpaperscissors.py:9 | Taint scissors | arg | +| rockpaperscissors.py:19 | x_0 | rockpaperscissors.py:19 | Taint rock | ROCK | +| rockpaperscissors.py:20 | x_1 | rockpaperscissors.py:19 | Taint rock | ROCK | +| rockpaperscissors.py:20 | y_0 | rockpaperscissors.py:20 | Taint scissors | Attribute() | +| rockpaperscissors.py:21 | y_1 | rockpaperscissors.py:20 | Taint scissors | Attribute() | +| rockpaperscissors.py:24 | x_0 | rockpaperscissors.py:24 | Taint rock | ROCK | +| rockpaperscissors.py:25 | x_1 | rockpaperscissors.py:24 | Taint rock | ROCK | +| rockpaperscissors.py:25 | y_0 | rockpaperscissors.py:25 | Taint paper | Attribute() | +| rockpaperscissors.py:26 | y_1 | rockpaperscissors.py:25 | Taint paper | Attribute() | +| rockpaperscissors.py:29 | x_0 | rockpaperscissors.py:29 | Taint scissors | SCISSORS | +| rockpaperscissors.py:30 | x_1 | rockpaperscissors.py:29 | Taint scissors | SCISSORS | +| rockpaperscissors.py:30 | y_0 | rockpaperscissors.py:30 | Taint paper | Attribute() | +| rockpaperscissors.py:31 | x_2 | rockpaperscissors.py:29 | Taint scissors | SCISSORS | +| rockpaperscissors.py:32 | y_1 | rockpaperscissors.py:30 | Taint paper | Attribute() | +| sanitizer.py:3 | arg_0 | sanitizer.py:3 | Taint Command injection | arg | +| sanitizer.py:3 | arg_0 | sanitizer.py:3 | Taint SQL injection | arg | +| sanitizer.py:5 | arg_0 | sanitizer.py:5 | Taint Command injection | arg | +| sanitizer.py:5 | arg_0 | sanitizer.py:5 | Taint SQL injection | arg | +| sanitizer.py:8 | x_6 | sanitizer.py:9 | Taint Command injection | user_input() | +| sanitizer.py:8 | x_6 | sanitizer.py:9 | Taint SQL injection | user_input() | +| sanitizer.py:9 | x_0 | sanitizer.py:9 | Taint Command injection | user_input() | +| sanitizer.py:9 | x_0 | sanitizer.py:9 | Taint SQL injection | user_input() | +| sanitizer.py:10 | x_1 | sanitizer.py:9 | Taint Command injection | user_input() | +| sanitizer.py:10 | x_1 | sanitizer.py:9 | Taint SQL injection | user_input() | +| sanitizer.py:11 | x_2 | sanitizer.py:9 | Taint Command injection | user_input() | +| sanitizer.py:11 | x_3 | sanitizer.py:9 | Taint Command injection | user_input() | +| sanitizer.py:13 | x_4 | sanitizer.py:9 | Taint Command injection | user_input() | +| sanitizer.py:13 | x_4 | sanitizer.py:9 | Taint SQL injection | user_input() | +| sanitizer.py:13 | x_5 | sanitizer.py:9 | Taint Command injection | user_input() | +| sanitizer.py:13 | x_5 | sanitizer.py:9 | Taint SQL injection | user_input() | +| sanitizer.py:15 | x_6 | sanitizer.py:16 | Taint Command injection | user_input() | +| sanitizer.py:15 | x_6 | sanitizer.py:16 | Taint SQL injection | user_input() | +| sanitizer.py:16 | x_0 | sanitizer.py:16 | Taint Command injection | user_input() | +| sanitizer.py:16 | x_0 | sanitizer.py:16 | Taint SQL injection | user_input() | +| sanitizer.py:17 | x_1 | sanitizer.py:16 | Taint Command injection | user_input() | +| sanitizer.py:17 | x_1 | sanitizer.py:16 | Taint SQL injection | user_input() | +| sanitizer.py:18 | x_2 | sanitizer.py:16 | Taint SQL injection | user_input() | +| sanitizer.py:18 | x_3 | sanitizer.py:16 | Taint SQL injection | user_input() | +| sanitizer.py:20 | x_4 | sanitizer.py:16 | Taint Command injection | user_input() | +| sanitizer.py:20 | x_4 | sanitizer.py:16 | Taint SQL injection | user_input() | +| sanitizer.py:20 | x_5 | sanitizer.py:16 | Taint Command injection | user_input() | +| sanitizer.py:20 | x_5 | sanitizer.py:16 | Taint SQL injection | user_input() | +| sanitizer.py:23 | x_6 | sanitizer.py:24 | Taint Command injection | user_input() | +| sanitizer.py:23 | x_6 | sanitizer.py:24 | Taint SQL injection | user_input() | +| sanitizer.py:24 | x_0 | sanitizer.py:24 | Taint Command injection | user_input() | +| sanitizer.py:24 | x_0 | sanitizer.py:24 | Taint SQL injection | user_input() | +| sanitizer.py:25 | x_1 | sanitizer.py:24 | Taint Command injection | user_input() | +| sanitizer.py:25 | x_1 | sanitizer.py:24 | Taint SQL injection | user_input() | +| sanitizer.py:26 | x_2 | sanitizer.py:24 | Taint Command injection | user_input() | +| sanitizer.py:26 | x_2 | sanitizer.py:24 | Taint SQL injection | user_input() | +| sanitizer.py:26 | x_3 | sanitizer.py:24 | Taint Command injection | user_input() | +| sanitizer.py:26 | x_3 | sanitizer.py:24 | Taint SQL injection | user_input() | +| sanitizer.py:28 | x_4 | sanitizer.py:24 | Taint Command injection | user_input() | +| sanitizer.py:28 | x_4 | sanitizer.py:24 | Taint SQL injection | user_input() | +| sanitizer.py:28 | x_5 | sanitizer.py:24 | Taint Command injection | user_input() | +| sanitizer.py:28 | x_5 | sanitizer.py:24 | Taint SQL injection | user_input() | +| sanitizer.py:30 | x_6 | sanitizer.py:31 | Taint Command injection | user_input() | +| sanitizer.py:30 | x_6 | sanitizer.py:31 | Taint SQL injection | user_input() | +| sanitizer.py:31 | x_0 | sanitizer.py:31 | Taint Command injection | user_input() | +| sanitizer.py:31 | x_0 | sanitizer.py:31 | Taint SQL injection | user_input() | +| sanitizer.py:32 | x_1 | sanitizer.py:31 | Taint Command injection | user_input() | +| sanitizer.py:32 | x_1 | sanitizer.py:31 | Taint SQL injection | user_input() | +| sanitizer.py:33 | x_2 | sanitizer.py:31 | Taint Command injection | user_input() | +| sanitizer.py:33 | x_2 | sanitizer.py:31 | Taint SQL injection | user_input() | +| sanitizer.py:33 | x_3 | sanitizer.py:31 | Taint Command injection | user_input() | +| sanitizer.py:33 | x_3 | sanitizer.py:31 | Taint SQL injection | user_input() | +| sanitizer.py:35 | x_4 | sanitizer.py:31 | Taint Command injection | user_input() | +| sanitizer.py:35 | x_4 | sanitizer.py:31 | Taint SQL injection | user_input() | +| sanitizer.py:35 | x_5 | sanitizer.py:31 | Taint Command injection | user_input() | +| sanitizer.py:35 | x_5 | sanitizer.py:31 | Taint SQL injection | user_input() | +| test.py:6 | s_0 | test.py:6 | Taint simple.test | SOURCE | +| test.py:7 | s_1 | test.py:6 | Taint simple.test | SOURCE | +| test.py:12 | arg_0 | test.py:12 | Taint simple.test | arg | +| test.py:13 | arg_1 | test.py:12 | Taint simple.test | arg | +| test.py:16 | t_0 | test.py:16 | Taint simple.test | source() | +| test.py:17 | t_1 | test.py:16 | Taint simple.test | source() | +| test.py:20 | t_0 | test.py:20 | Taint simple.test | SOURCE | +| test.py:21 | t_1 | test.py:20 | Taint simple.test | SOURCE | +| test.py:24 | t_0 | test.py:24 | Taint simple.test | source() | +| test.py:25 | t_1 | test.py:24 | Taint simple.test | source() | +| test.py:31 | t_2 | test.py:31 | Taint simple.test | SOURCE | +| test.py:37 | t_0 | test.py:37 | Taint simple.test | SOURCE | +| test.py:41 | t_1 | test.py:37 | Taint simple.test | SOURCE | +| test.py:46 | arg_0 | test.py:46 | Taint simple.test | arg | +| test.py:47 | arg_1 | test.py:46 | Taint simple.test | arg | +| test.py:49 | arg_0 | test.py:49 | Taint simple.test | arg | +| test.py:49 | arg_2 | test.py:49 | Taint simple.test | arg | +| test.py:51 | arg_1 | test.py:49 | Taint simple.test | arg | +| test.py:54 | t_0 | test.py:54 | Taint simple.test | source2() | +| test.py:55 | t_1 | test.py:54 | Taint simple.test | source2() | +| test.py:62 | t_1 | test.py:62 | Taint simple.test | SOURCE | +| test.py:63 | t_2 | test.py:62 | Taint simple.test | SOURCE | +| test.py:67 | t_0 | test.py:67 | Taint simple.test | SOURCE | +| test.py:70 | t_2 | test.py:67 | Taint simple.test | SOURCE | +| test.py:72 | arg_0 | test.py:72 | Attribute 'x' taint simple.test | arg | +| test.py:72 | arg_0 | test.py:72 | Taint basic.custom | arg | +| test.py:72 | arg_0 | test.py:72 | Taint simple.test | arg | +| test.py:76 | t_0 | test.py:76 | Taint simple.test | SOURCE | +| test.py:77 | t_1 | test.py:77 | Taint simple.test | hub() | +| test.py:78 | t_2 | test.py:77 | Taint simple.test | hub() | +| test.py:85 | module_0 | test.py:85 | Attribute 'dangerous' taint simple.test | ImportExpr | +| test.py:87 | module_1 | test.py:85 | Attribute 'dangerous' taint simple.test | ImportExpr | +| test.py:88 | t_0 | test.py:88 | Taint simple.test | Attribute | +| test.py:89 | t_1 | test.py:88 | Taint simple.test | Attribute | +| test.py:91 | module_2 | test.py:85 | Attribute 'dangerous' taint simple.test | ImportExpr | +| test.py:95 | module_3 | test.py:85 | Attribute 'dangerous' taint simple.test | ImportExpr | +| test.py:99 | module_4 | test.py:85 | Attribute 'dangerous' taint simple.test | ImportExpr | +| test.py:100 | t_0 | test.py:100 | Taint simple.test | Attribute() | +| test.py:101 | t_1 | test.py:100 | Taint simple.test | Attribute() | +| test.py:105 | arg_0 | test.py:105 | Attribute 'x' taint simple.test | arg | +| test.py:108 | module_5 | test.py:85 | Attribute 'dangerous' taint simple.test | ImportExpr | +| test.py:110 | t_1 | test.py:110 | Attribute 'x' taint simple.test | t | +| test.py:113 | module_6 | test.py:85 | Attribute 'dangerous' taint simple.test | ImportExpr | +| test.py:115 | t_1 | test.py:115 | Attribute 'x' taint simple.test | t | +| test.py:116 | t_2 | test.py:116 | Attribute 'x' taint simple.test | hub() | +| test.py:117 | t_3 | test.py:116 | Attribute 'x' taint simple.test | hub() | +| test.py:120 | t_0 | test.py:120 | Taint basic.custom | CUSTOM_SOURCE | +| test.py:121 | t_1 | test.py:121 | Taint basic.custom | hub() | +| test.py:122 | t_2 | test.py:121 | Taint basic.custom | hub() | +| test.py:126 | t_0 | test.py:126 | Taint basic.custom | CUSTOM_SOURCE | +| test.py:128 | t_2 | test.py:128 | Taint simple.test | SOURCE | +| test.py:130 | t_1 | test.py:126 | Taint basic.custom | CUSTOM_SOURCE | +| test.py:132 | t_3 | test.py:128 | Taint simple.test | SOURCE | +| test.py:136 | t_0 | test.py:136 | Taint basic.custom | CUSTOM_SOURCE | +| test.py:138 | t_2 | test.py:138 | Taint simple.test | SOURCE | +| test.py:140 | t_3 | test.py:138 | Taint simple.test | SOURCE | +| test.py:142 | t_1 | test.py:136 | Taint basic.custom | CUSTOM_SOURCE | +| test.py:146 | t_0 | test.py:146 | Taint basic.custom | CUSTOM_SOURCE | +| test.py:148 | t_3 | test.py:148 | Taint simple.test | SOURCE | +| test.py:149 | t_1 | test.py:149 | Taint basic.custom | TAINT_FROM_ARG() | +| test.py:151 | t_2 | test.py:149 | Taint basic.custom | TAINT_FROM_ARG() | +| test.py:155 | unsafe_0 | test.py:155 | Taint simple.test | ImportMember | +| test.py:156 | unsafe_1 | test.py:155 | Taint simple.test | ImportMember | +| test.py:159 | t_0 | test.py:159 | Taint simple.test | SOURCE | +| test.py:160 | t_1 | test.py:159 | Taint simple.test | SOURCE | +| test.py:163 | s_0 | test.py:163 | Taint simple.test | SOURCE | +| test.py:168 | l_0 | test.py:168 | Taint [simple.test] | List | +| test.py:169 | d_0 | test.py:169 | Taint {simple.test} | Dict | +| test.py:170 | l_1 | test.py:168 | Taint [simple.test] | List | +| test.py:170 | x_1 | test.py:170 | Taint [simple.test] | l | +| test.py:171 | d_1 | test.py:169 | Taint {simple.test} | Dict | +| test.py:171 | y_1 | test.py:171 | Taint {simple.test} | d | +| test.py:174 | l2_0 | test.py:174 | Taint [simple.test] | list() | +| test.py:174 | l_2 | test.py:168 | Taint [simple.test] | List | +| test.py:175 | d2_0 | test.py:175 | Taint {simple.test} | dict() | +| test.py:175 | d_2 | test.py:169 | Taint {simple.test} | Dict | diff --git a/python/ql/test/library-tests/taint/general/TestVar.ql b/python/ql/test/library-tests/taint/general/TestVar.ql new file mode 100644 index 000000000000..93a90e7bd76f --- /dev/null +++ b/python/ql/test/library-tests/taint/general/TestVar.ql @@ -0,0 +1,9 @@ +import python +import semmle.python.security.TaintTest +import TaintLib + + +from EssaVariable var, TaintedNode n +where TaintFlowTest::tainted_var(var, _, n) +select + var.getDefinition().getLocation().toString(), var.getRepresentation(), n.getLocation().toString(), n.getTrackedValue(), n.getNode().getNode().toString() diff --git a/python/ql/test/library-tests/taint/general/carrier.py b/python/ql/test/library-tests/taint/general/carrier.py new file mode 100644 index 000000000000..10e70e41a933 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/carrier.py @@ -0,0 +1,35 @@ + +class ImplicitCarrier(object): + + def __init__(self, arg): + self.attr = arg + + def set_attr(self, arg): + self.attr = arg + + def get_attr(self): + return self.attr + +def hub(arg): + return arg + +def test1(): + c = ImplicitCarrier(SOURCE) + SINK(c.attr) + +def test2(): + c = TAINT_CARRIER_SOURCE + SINK(c.get_taint()) + +def test3(): + c = hub(ImplicitCarrier(SOURCE)) + SINK(c.get_attr()) + +def test4(): + c = hub(TAINT_CARRIER_SOURCE) + SINK(c.get_taint()) + +def test5(): + c = ImplicitCarrier(TAINT_CARRIER_SOURCE) + x = c.attr + SINK(x.get_taint()) diff --git a/python/ql/test/library-tests/taint/general/deep.py b/python/ql/test/library-tests/taint/general/deep.py new file mode 100644 index 000000000000..022cf403f7ac --- /dev/null +++ b/python/ql/test/library-tests/taint/general/deep.py @@ -0,0 +1,24 @@ + +def f1(arg): + return arg + +def f2(arg): + return f1(arg) + +def f3(arg): + return f2(arg) + +def f4(arg): + return f3(arg) + +def f5(arg): + return f4(arg) + +def f6(arg): + return f5(arg) + +x = f6(SOURCE) + +x + + diff --git a/python/ql/test/library-tests/taint/general/module.py b/python/ql/test/library-tests/taint/general/module.py new file mode 100644 index 000000000000..437ef3bb2392 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/module.py @@ -0,0 +1,11 @@ + + +dangerous = SOURCE +safe = "safe" + +def dangerous_func(): + return SOURCE + + +safe2 = SOURCE +safe2 = "safe" diff --git a/python/ql/test/library-tests/taint/general/rockpaperscissors.py b/python/ql/test/library-tests/taint/general/rockpaperscissors.py new file mode 100644 index 000000000000..8d73e090e336 --- /dev/null +++ b/python/ql/test/library-tests/taint/general/rockpaperscissors.py @@ -0,0 +1,32 @@ + + +def rock(arg): + "SCISSORS are vulnerable" + +def paper(arg): + "ROCK is vulnerable" + +def scissors(arg): + "PAPER is vulnerable" + +def test1(): + rock(SCISSORS) + +def test2(): + paper(ROCK) + +def test3(): + x = ROCK + y = x.prev() #scissors + scissors(y) + +def test4(): + x = ROCK + y = x.prev().prev() # paper + scissors(y) + +def test5(): + x = SCISSORS + y = x.prev() # paper + paper(x) + paper(y) diff --git a/python/ql/test/library-tests/taint/general/sanitizer.py b/python/ql/test/library-tests/taint/general/sanitizer.py new file mode 100644 index 000000000000..9238f66fb32d --- /dev/null +++ b/python/ql/test/library-tests/taint/general/sanitizer.py @@ -0,0 +1,36 @@ + +#Sanitizer functions +def isEscapedSql(arg): pass + +def isValidCommand(arg): pass + + +def sql_inject1(): + x = user_input() + if isEscapedSql(x): + sql_query(x) # Safe + else: + sql_query(x) # DANGEROUS + +def command_inject1(): + x = user_input() + if isValidCommand(x): + os_command(x) # Safe + else: + os_command(x) # DANGEROUS + + +def sql_inject2(): + x = user_input() + if notASanitizer(x): + sql_query(x) # DANGEROUS + else: + sql_query(x) # DANGEROUS + +def command_inject2(): + x = user_input() + if notASanitizer(x): + os_command(x) # DANGEROUS + else: + os_command(x) # DANGEROUS + diff --git a/python/ql/test/library-tests/taint/general/test.py b/python/ql/test/library-tests/taint/general/test.py new file mode 100644 index 000000000000..5a6fc1e2900d --- /dev/null +++ b/python/ql/test/library-tests/taint/general/test.py @@ -0,0 +1,175 @@ + +def test1(): + SINK(SOURCE) + +def test2(): + s = SOURCE + SINK(s) + +def source(): + return SOURCE + +def sink(arg): + SINK(arg) + +def test3(): + t = source() + SINK(t) + +def test4(): + t = SOURCE + sink(t) + +def test5(): + t = source() + sink(t) + +def test6(cond): + if cond: + t = "Safe" + else: + t = SOURCE + if cond: + SINK(t) + +def test7(cond): + if cond: + t = SOURCE + else: + t = "Safe" + if cond: + SINK(t) + +def source2(arg): + return source(arg) + +def sink2(arg): + sink(arg) + +def sink3(cond, arg): + if cond: + sink(arg) + +def test8(cond): + t = source2() + sink2(t) + +#False positive +def test9(cond): + if cond: + t = "Safe" + else: + t = SOURCE + sink3(cond, t) + +def test10(cond): + if cond: + t = SOURCE + else: + t = "Safe" + sink3(cond, t) + +def hub(arg): + return arg + +def test11(): + t = SOURCE + t = hub(t) + SINK(t) + +def test12(): + t = "safe" + t = hub(t) + SINK(t) + +import module + +def test13(): + t = module.dangerous + SINK(t) + +def test14(): + t = module.safe + SINK(t) + +def test15(): + t = module.safe2 + SINK(t) + +def test16(): + t = module.dangerous_func() + SINK(t) + +class C(object): pass + +def x_sink(arg): + SINK(arg.x) + +def test17(): + t = C() + t.x = module.dangerous + SINK(t.x) + +def test18(): + t = C() + t.x = module.dangerous + t = hub(t) + x_sink(t) + +def test19(): + t = CUSTOM_SOURCE + t = hub(TAINT_FROM_ARG(t)) + CUSTOM_SINK(t) + +def test20(cond): + if cond: + t = CUSTOM_SOURCE + else: + t = SOURCE + if cond: + CUSTOM_SINK(t) + else: + SINK(t) + +def test21(cond): + if cond: + t = CUSTOM_SOURCE + else: + t = SOURCE + if not cond: + CUSTOM_SINK(t) + else: + SINK(t) + +def test22(cond): + if cond: + t = CUSTOM_SOURCE + else: + t = SOURCE + t = TAINT_FROM_ARG(t) + if cond: + CUSTOM_SINK(t) + else: + SINK(t) + +from module import dangerous as unsafe +SINK(unsafe) + +def test23(): + with SOURCE as t: + SINK(t) + +def test24(): + s = SOURCE + SANITIZE(s) + SINK(s) + +def test_update_extend(x, y): + l = [SOURCE] + d = {"key" : SOURCE} + x.extend(l) + y.update(d) + SINK(x[0]) + SINK(y["key"]) + l2 = list(l) + d2 = dict(d) diff --git a/python/ql/test/library-tests/taint/invalid/NoSink.expected b/python/ql/test/library-tests/taint/invalid/NoSink.expected new file mode 100644 index 000000000000..566c01caa9b7 --- /dev/null +++ b/python/ql/test/library-tests/taint/invalid/NoSink.expected @@ -0,0 +1 @@ +| No sinks defined | This message wouldn't appear if the query were complete $@ | No sinks defined | nor this | diff --git a/python/ql/test/library-tests/taint/invalid/NoSink.ql b/python/ql/test/library-tests/taint/invalid/NoSink.ql new file mode 100644 index 000000000000..822de8a622a8 --- /dev/null +++ b/python/ql/test/library-tests/taint/invalid/NoSink.ql @@ -0,0 +1,25 @@ + +import python + +import semmle.python.security.TaintTracking + +/* Sources */ + +class AnySource extends TaintSource { + + AnySource() { + this instanceof ControlFlowNode + } + + override predicate isSourceOf(TaintKind kind) { any() } + +} +/* Flow */ +import semmle.python.security.strings.Untrusted + +from TaintSource src, TaintSink sink +where src.flowsToSink(sink) + +select sink.toString(), "This message wouldn't appear if the query were complete $@", + src.toString(), "nor this" + diff --git a/python/ql/test/library-tests/taint/invalid/NoSource.expected b/python/ql/test/library-tests/taint/invalid/NoSource.expected new file mode 100644 index 000000000000..1d8515200e96 --- /dev/null +++ b/python/ql/test/library-tests/taint/invalid/NoSource.expected @@ -0,0 +1 @@ +| No sources defined | This message wouldn't appear if the query were complete $@ | No sources defined | nor this | diff --git a/python/ql/test/library-tests/taint/invalid/NoSource.ql b/python/ql/test/library-tests/taint/invalid/NoSource.ql new file mode 100644 index 000000000000..1b7f2067a08c --- /dev/null +++ b/python/ql/test/library-tests/taint/invalid/NoSource.ql @@ -0,0 +1,26 @@ + +import python + +import semmle.python.security.TaintTracking + +/* Flow */ +import semmle.python.security.strings.Untrusted + +/* Sinks */ + +class AnySink extends TaintSink{ + + AnySink() { + this instanceof ControlFlowNode + } + + override predicate sinks(TaintKind kind) { any() } + +} + +from TaintSource src, TaintSink sink +where src.flowsToSink(sink) + +select sink.toString(), "This message wouldn't appear if the query were complete $@", + src.toString(), "nor this" + diff --git a/python/ql/test/library-tests/taint/invalid/test.py b/python/ql/test/library-tests/taint/invalid/test.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/taint/strings/DistinctStringKinds.expected b/python/ql/test/library-tests/taint/strings/DistinctStringKinds.expected new file mode 100644 index 000000000000..684c12a37467 --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/DistinctStringKinds.expected @@ -0,0 +1,16 @@ +| Taint exception.info | test.py:54 | test.py:54:22:54:26 | taint | test.py:59 | +| Taint exception.info | test.py:55 | test.py:55:12:55:22 | func() | test.py:59 | +| Taint exception.info | test.py:55 | test.py:55:17:55:21 | taint | test.py:59 | +| Taint exception.info | test.py:58 | test.py:58:12:58:33 | TAINTED_EXCEPTION_INFO | | +| Taint exception.info | test.py:59 | test.py:59:11:59:41 | cross_over() | | +| Taint exception.info | test.py:59 | test.py:59:37:59:40 | info | | +| Taint exception.info | test.py:61 | test.py:61:19:61:21 | arg | test.py:55 from test.py:59 | +| Taint exception.info | test.py:62 | test.py:62:12:62:14 | arg | test.py:55 from test.py:59 | +| Taint externally controlled string | test.py:54 | test.py:54:22:54:26 | taint | test.py:66 | +| Taint externally controlled string | test.py:55 | test.py:55:12:55:22 | func() | test.py:66 | +| Taint externally controlled string | test.py:55 | test.py:55:17:55:21 | taint | test.py:66 | +| Taint externally controlled string | test.py:61 | test.py:61:19:61:21 | arg | test.py:55 from test.py:66 | +| Taint externally controlled string | test.py:62 | test.py:62:12:62:14 | arg | test.py:55 from test.py:66 | +| Taint externally controlled string | test.py:65 | test.py:65:11:65:33 | TAINTED_EXTERNAL_STRING | | +| Taint externally controlled string | test.py:66 | test.py:66:11:66:41 | cross_over() | | +| Taint externally controlled string | test.py:66 | test.py:66:38:66:40 | ext | | diff --git a/python/ql/test/library-tests/taint/strings/DistinctStringKinds.ql b/python/ql/test/library-tests/taint/strings/DistinctStringKinds.ql new file mode 100644 index 000000000000..4165154b4aa7 --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/DistinctStringKinds.ql @@ -0,0 +1,39 @@ +import python +import semmle.python.security.TaintTracking + +import semmle.python.security.Exceptions +import semmle.python.security.strings.Untrusted + + +class ExceptionInfoSource extends TaintSource { + + ExceptionInfoSource() { this.(NameNode).getId() = "TAINTED_EXCEPTION_INFO" } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExceptionInfo + } + + override string toString() { + result = "Exception info source" + } + +} + +class ExternalStringSource extends TaintSource { + + ExternalStringSource() { this.(NameNode).getId() = "TAINTED_EXTERNAL_STRING" } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExternalStringKind + } + + override string toString() { + result = "Untrusted string source" + } + +} + +from TaintedNode n +where n.getLocation().getFile().getName().matches("%test.py") +select n.getTrackedValue(), n.getLocation().toString(), n.getNode().getNode(), n.getContext() + diff --git a/python/ql/test/library-tests/taint/strings/Taint.qll b/python/ql/test/library-tests/taint/strings/Taint.qll new file mode 100644 index 000000000000..d39487b5ad32 --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/Taint.qll @@ -0,0 +1,47 @@ +import python +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Untrusted + + +class SimpleSource extends TaintSource { + + SimpleSource() { this.(NameNode).getId() = "TAINTED" } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExternalStringKind + } + + string toString() { + result = "taint source" + } + +} + +class ListSource extends TaintSource { + + ListSource() { this.(NameNode).getId() = "TAINTED_LIST" } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExternalStringSequenceKind + } + + string toString() { + result = "list taint source" + } + +} + +class DictSource extends TaintSource { + + DictSource() { this.(NameNode).getId() = "TAINTED_DICT" } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExternalStringDictKind + } + + string toString() { + result = "dict taint source" + } + +} + diff --git a/python/ql/test/library-tests/taint/strings/TestNode.expected b/python/ql/test/library-tests/taint/strings/TestNode.expected new file mode 100644 index 000000000000..033bdf81d17d --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/TestNode.expected @@ -0,0 +1,59 @@ +| Taint [externally controlled string] | test.py:12 | test.py:12:20:12:31 | TAINTED_LIST | | +| Taint [externally controlled string] | test.py:13 | test.py:13:9:13:20 | tainted_list | | +| Taint [externally controlled string] | test.py:14 | test.py:14:9:14:20 | tainted_list | | +| Taint [externally controlled string] | test.py:15 | test.py:15:9:15:20 | tainted_list | | +| Taint [externally controlled string] | test.py:15 | test.py:15:9:15:25 | Subscript | | +| Taint [externally controlled string] | test.py:16 | test.py:16:9:16:20 | tainted_list | | +| Taint [externally controlled string] | test.py:16 | test.py:16:9:16:27 | Attribute() | | +| Taint externally controlled string | test.py:5 | test.py:5:22:5:28 | TAINTED | | +| Taint externally controlled string | test.py:6 | test.py:6:31:6:44 | tainted_string | | +| Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | +| Taint externally controlled string | test.py:8 | test.py:8:9:8:9 | a | | +| Taint externally controlled string | test.py:8 | test.py:8:9:8:18 | Attribute() | | +| Taint externally controlled string | test.py:9 | test.py:9:9:9:9 | b | | +| Taint externally controlled string | test.py:9 | test.py:9:9:9:14 | Subscript | | +| Taint externally controlled string | test.py:13 | test.py:13:9:13:23 | Subscript | | +| Taint externally controlled string | test.py:14 | test.py:14:9:14:23 | Subscript | | +| Taint externally controlled string | test.py:20 | test.py:20:9:20:28 | Subscript | | +| Taint externally controlled string | test.py:21 | test.py:21:9:21:23 | Subscript | | +| Taint externally controlled string | test.py:25 | test.py:25:22:25:28 | TAINTED | | +| Taint externally controlled string | test.py:26 | test.py:26:9:26:22 | tainted_string | | +| Taint externally controlled string | test.py:26 | test.py:26:9:26:31 | Attribute() | | +| Taint externally controlled string | test.py:27 | test.py:27:9:27:22 | tainted_string | | +| Taint externally controlled string | test.py:27 | test.py:27:9:27:29 | Attribute() | | +| Taint externally controlled string | test.py:28 | test.py:28:9:28:22 | tainted_string | | +| Taint externally controlled string | test.py:28 | test.py:28:9:28:25 | Subscript | | +| Taint externally controlled string | test.py:29 | test.py:29:9:29:22 | tainted_string | | +| Taint externally controlled string | test.py:29 | test.py:29:9:29:27 | Subscript | | +| Taint externally controlled string | test.py:30 | test.py:30:9:30:32 | reversed() | | +| Taint externally controlled string | test.py:30 | test.py:30:18:30:31 | tainted_string | | +| Taint externally controlled string | test.py:31 | test.py:31:9:31:28 | copy() | | +| Taint externally controlled string | test.py:31 | test.py:31:14:31:27 | tainted_string | | +| Taint externally controlled string | test.py:32 | test.py:32:9:32:22 | tainted_string | | +| Taint externally controlled string | test.py:32 | test.py:32:9:32:30 | Attribute() | | +| Taint externally controlled string | test.py:35 | test.py:35:22:35:28 | TAINTED | | +| Taint externally controlled string | test.py:36 | test.py:36:8:36:21 | tainted_string | | +| Taint externally controlled string | test.py:39 | test.py:39:23:39:36 | tainted_string | | +| Taint externally controlled string | test.py:42 | test.py:42:22:42:28 | TAINTED | | +| Taint externally controlled string | test.py:43 | test.py:43:8:43:21 | tainted_string | | +| Taint externally controlled string | test.py:43 | test.py:43:34:43:47 | tainted_string | | +| Taint externally controlled string | test.py:46 | test.py:46:23:46:36 | tainted_string | | +| Taint externally controlled string | test.py:49 | test.py:49:22:49:28 | TAINTED | | +| Taint externally controlled string | test.py:50 | test.py:50:9:50:27 | str() | | +| Taint externally controlled string | test.py:50 | test.py:50:13:50:26 | tainted_string | | +| Taint externally controlled string | test.py:51 | test.py:51:9:51:29 | bytes() | | +| Taint externally controlled string | test.py:51 | test.py:51:15:51:28 | tainted_string | | +| Taint externally controlled string | test.py:52 | test.py:52:9:52:46 | bytes() | | +| Taint externally controlled string | test.py:52 | test.py:52:15:52:28 | tainted_string | | +| Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | +| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | +| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | +| Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | +| Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:18 | Attribute() | | +| Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | +| Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:14 | Subscript | | +| Taint {externally controlled string} | test.py:19 | test.py:19:20:19:31 | TAINTED_DICT | | +| Taint {externally controlled string} | test.py:20 | test.py:20:9:20:20 | tainted_dict | | +| Taint {externally controlled string} | test.py:21 | test.py:21:9:21:20 | tainted_dict | | +| Taint {externally controlled string} | test.py:22 | test.py:22:9:22:20 | tainted_dict | | +| Taint {externally controlled string} | test.py:22 | test.py:22:9:22:27 | Attribute() | | diff --git a/python/ql/test/library-tests/taint/strings/TestNode.ql b/python/ql/test/library-tests/taint/strings/TestNode.ql new file mode 100644 index 000000000000..1210189a6a0a --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/TestNode.ql @@ -0,0 +1,9 @@ +import python +import semmle.python.security.TaintTracking +import Taint + + +from TaintedNode n +where n.getLocation().getFile().getName().matches("%test.py") +select n.getTrackedValue(), n.getLocation().toString(), n.getNode().getNode(), n.getContext() + diff --git a/python/ql/test/library-tests/taint/strings/TestStep.expected b/python/ql/test/library-tests/taint/strings/TestStep.expected new file mode 100644 index 000000000000..87ca159c2aff --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/TestStep.expected @@ -0,0 +1,52 @@ +| Taint [externally controlled string] | test.py:12 | test.py:12:20:12:31 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:13 | test.py:13:9:13:20 | tainted_list | | +| Taint [externally controlled string] | test.py:12 | test.py:12:20:12:31 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:14 | test.py:14:9:14:20 | tainted_list | | +| Taint [externally controlled string] | test.py:12 | test.py:12:20:12:31 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:15 | test.py:15:9:15:20 | tainted_list | | +| Taint [externally controlled string] | test.py:12 | test.py:12:20:12:31 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:16 | test.py:16:9:16:20 | tainted_list | | +| Taint [externally controlled string] | test.py:13 | test.py:13:9:13:20 | tainted_list | | --> | Taint externally controlled string | test.py:13 | test.py:13:9:13:23 | Subscript | | +| Taint [externally controlled string] | test.py:14 | test.py:14:9:14:20 | tainted_list | | --> | Taint externally controlled string | test.py:14 | test.py:14:9:14:23 | Subscript | | +| Taint [externally controlled string] | test.py:15 | test.py:15:9:15:20 | tainted_list | | --> | Taint [externally controlled string] | test.py:15 | test.py:15:9:15:25 | Subscript | | +| Taint [externally controlled string] | test.py:16 | test.py:16:9:16:20 | tainted_list | | --> | Taint [externally controlled string] | test.py:16 | test.py:16:9:16:27 | Attribute() | | +| Taint externally controlled string | test.py:5 | test.py:5:22:5:28 | TAINTED | | --> | Taint externally controlled string | test.py:6 | test.py:6:31:6:44 | tainted_string | | +| Taint externally controlled string | test.py:6 | test.py:6:31:6:44 | tainted_string | | --> | Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | +| Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | --> | Taint externally controlled string | test.py:8 | test.py:8:9:8:9 | a | | +| Taint externally controlled string | test.py:8 | test.py:8:9:8:18 | Attribute() | | --> | Taint externally controlled string | test.py:9 | test.py:9:9:9:9 | b | | +| Taint externally controlled string | test.py:25 | test.py:25:22:25:28 | TAINTED | | --> | Taint externally controlled string | test.py:26 | test.py:26:9:26:22 | tainted_string | | +| Taint externally controlled string | test.py:25 | test.py:25:22:25:28 | TAINTED | | --> | Taint externally controlled string | test.py:27 | test.py:27:9:27:22 | tainted_string | | +| Taint externally controlled string | test.py:25 | test.py:25:22:25:28 | TAINTED | | --> | Taint externally controlled string | test.py:28 | test.py:28:9:28:22 | tainted_string | | +| Taint externally controlled string | test.py:25 | test.py:25:22:25:28 | TAINTED | | --> | Taint externally controlled string | test.py:29 | test.py:29:9:29:22 | tainted_string | | +| Taint externally controlled string | test.py:25 | test.py:25:22:25:28 | TAINTED | | --> | Taint externally controlled string | test.py:30 | test.py:30:18:30:31 | tainted_string | | +| Taint externally controlled string | test.py:25 | test.py:25:22:25:28 | TAINTED | | --> | Taint externally controlled string | test.py:31 | test.py:31:14:31:27 | tainted_string | | +| Taint externally controlled string | test.py:25 | test.py:25:22:25:28 | TAINTED | | --> | Taint externally controlled string | test.py:32 | test.py:32:9:32:22 | tainted_string | | +| Taint externally controlled string | test.py:26 | test.py:26:9:26:22 | tainted_string | | --> | Taint externally controlled string | test.py:26 | test.py:26:9:26:31 | Attribute() | | +| Taint externally controlled string | test.py:27 | test.py:27:9:27:22 | tainted_string | | --> | Taint externally controlled string | test.py:27 | test.py:27:9:27:29 | Attribute() | | +| Taint externally controlled string | test.py:28 | test.py:28:9:28:22 | tainted_string | | --> | Taint externally controlled string | test.py:28 | test.py:28:9:28:25 | Subscript | | +| Taint externally controlled string | test.py:29 | test.py:29:9:29:22 | tainted_string | | --> | Taint externally controlled string | test.py:29 | test.py:29:9:29:27 | Subscript | | +| Taint externally controlled string | test.py:30 | test.py:30:18:30:31 | tainted_string | | --> | Taint externally controlled string | test.py:30 | test.py:30:9:30:32 | reversed() | | +| Taint externally controlled string | test.py:31 | test.py:31:14:31:27 | tainted_string | | --> | Taint externally controlled string | test.py:31 | test.py:31:9:31:28 | copy() | | +| Taint externally controlled string | test.py:32 | test.py:32:9:32:22 | tainted_string | | --> | Taint externally controlled string | test.py:32 | test.py:32:9:32:30 | Attribute() | | +| Taint externally controlled string | test.py:35 | test.py:35:22:35:28 | TAINTED | | --> | Taint externally controlled string | test.py:36 | test.py:36:8:36:21 | tainted_string | | +| Taint externally controlled string | test.py:35 | test.py:35:22:35:28 | TAINTED | | --> | Taint externally controlled string | test.py:39 | test.py:39:23:39:36 | tainted_string | | +| Taint externally controlled string | test.py:42 | test.py:42:22:42:28 | TAINTED | | --> | Taint externally controlled string | test.py:43 | test.py:43:8:43:21 | tainted_string | | +| Taint externally controlled string | test.py:42 | test.py:42:22:42:28 | TAINTED | | --> | Taint externally controlled string | test.py:43 | test.py:43:34:43:47 | tainted_string | | +| Taint externally controlled string | test.py:42 | test.py:42:22:42:28 | TAINTED | | --> | Taint externally controlled string | test.py:46 | test.py:46:23:46:36 | tainted_string | | +| Taint externally controlled string | test.py:49 | test.py:49:22:49:28 | TAINTED | | --> | Taint externally controlled string | test.py:50 | test.py:50:13:50:26 | tainted_string | | +| Taint externally controlled string | test.py:49 | test.py:49:22:49:28 | TAINTED | | --> | Taint externally controlled string | test.py:51 | test.py:51:15:51:28 | tainted_string | | +| Taint externally controlled string | test.py:49 | test.py:49:22:49:28 | TAINTED | | --> | Taint externally controlled string | test.py:52 | test.py:52:15:52:28 | tainted_string | | +| Taint externally controlled string | test.py:50 | test.py:50:13:50:26 | tainted_string | | --> | Taint externally controlled string | test.py:50 | test.py:50:9:50:27 | str() | | +| Taint externally controlled string | test.py:51 | test.py:51:15:51:28 | tainted_string | | --> | Taint externally controlled string | test.py:51 | test.py:51:9:51:29 | bytes() | | +| Taint externally controlled string | test.py:52 | test.py:52:15:52:28 | tainted_string | | --> | Taint externally controlled string | test.py:52 | test.py:52:9:52:46 | bytes() | | +| Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | +| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | +| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | +| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | --> | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | +| Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | --> | Taint externally controlled string | test.py:8 | test.py:8:9:8:18 | Attribute() | | +| Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | --> | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:18 | Attribute() | | +| Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:18 | Attribute() | | --> | Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | +| Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | --> | Taint externally controlled string | test.py:9 | test.py:9:9:9:14 | Subscript | | +| Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | --> | Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:14 | Subscript | | +| Taint {externally controlled string} | test.py:19 | test.py:19:20:19:31 | TAINTED_DICT | | --> | Taint {externally controlled string} | test.py:20 | test.py:20:9:20:20 | tainted_dict | | +| Taint {externally controlled string} | test.py:19 | test.py:19:20:19:31 | TAINTED_DICT | | --> | Taint {externally controlled string} | test.py:21 | test.py:21:9:21:20 | tainted_dict | | +| Taint {externally controlled string} | test.py:19 | test.py:19:20:19:31 | TAINTED_DICT | | --> | Taint {externally controlled string} | test.py:22 | test.py:22:9:22:20 | tainted_dict | | +| Taint {externally controlled string} | test.py:20 | test.py:20:9:20:20 | tainted_dict | | --> | Taint externally controlled string | test.py:20 | test.py:20:9:20:28 | Subscript | | +| Taint {externally controlled string} | test.py:21 | test.py:21:9:21:20 | tainted_dict | | --> | Taint externally controlled string | test.py:21 | test.py:21:9:21:23 | Subscript | | +| Taint {externally controlled string} | test.py:22 | test.py:22:9:22:20 | tainted_dict | | --> | Taint {externally controlled string} | test.py:22 | test.py:22:9:22:27 | Attribute() | | diff --git a/python/ql/test/library-tests/taint/strings/TestStep.ql b/python/ql/test/library-tests/taint/strings/TestStep.ql new file mode 100644 index 000000000000..90a4f5b4c3e0 --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/TestStep.ql @@ -0,0 +1,13 @@ +import python +import semmle.python.security.TaintTracking +import Taint + + +from TaintedNode n, TaintedNode s +where n.getLocation().getFile().getName().matches("%test.py") and +s.getLocation().getFile().getName().matches("%test.py") and +s = n.getASuccessor() +select + n.getTrackedValue(), n.getLocation().toString(), n.getNode().getNode(), n.getContext(), + " --> ", + s.getTrackedValue(), s.getLocation().toString(), s.getNode().getNode(), s.getContext() diff --git a/python/ql/test/library-tests/taint/strings/test.py b/python/ql/test/library-tests/taint/strings/test.py new file mode 100644 index 000000000000..3e359bf663ff --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/test.py @@ -0,0 +1,69 @@ +import json +from copy import copy + +def test_json(): + tainted_string = TAINTED + tainted_json = json.loads(tainted_string) + a = tainted_json["x"] + b = a.get("y") + c = b["z"] + +def test_list(x, y, z): + tainted_list = TAINTED_LIST + a = tainted_list[0] + b = tainted_list[x] + c = tainted_list[y:z] + d = tainted_list.copy() + +def test_dict(x): + tainted_dict = TAINTED_DICT + a = tainted_dict["name"] + b = tainted_dict[x] + c = tainted_dict.copy() + +def test_str(): + tainted_string = TAINTED + a = tainted_string.ljust(8) + b = tainted_string.copy() + c = tainted_string[:] + d = tainted_string[::2] + e = reversed(tainted_string) + f = copy(tainted_string) + h = tainted_string.strip() + +def test_const_sanitizer1(): + tainted_string = TAINTED + if tainted_string == "OK": + not_tainted(tainted_string) + else: + still_tainted(tainted_string) + +def test_const_sanitizer2(): + tainted_string = TAINTED + if tainted_string == "OK" or tainted_string == "ALSO_OK": + not_tainted(tainted_string) + else: + still_tainted(tainted_string) + +def test_str2(): + tainted_string = TAINTED + a = str(tainted_string) + b = bytes(tainted_string) # This is an error in Python 3 + c = bytes(tainted_string, encoding="utf8") # This is an error in Python 2 + +def cross_over(func, taint): + return func(taint) + +def test_exc_info(): + info = TAINTED_EXCEPTION_INFO + res = cross_over(exc_info_call, info) + +def exc_info_call(arg): + return arg + +def test_untrusted(): + ext = TAINTED_EXTERNAL_STRING + res = cross_over(untrusted_call, ext) + +def exc_untrusted_call(arg): + return arg diff --git a/python/ql/test/library-tests/thrift/Child.expected b/python/ql/test/library-tests/thrift/Child.expected new file mode 100644 index 000000000000..2a844932a865 --- /dev/null +++ b/python/ql/test/library-tests/thrift/Child.expected @@ -0,0 +1,571 @@ +| extended.thrift:3:8:3:11 | definition | 0 | extended.thrift:3:8:3:11 | struct User | +| extended.thrift:3:8:3:11 | name | 0 | extended.thrift:3:8:3:11 | IDENTIFIER User | +| extended.thrift:3:8:3:11 | struct User | 0 | extended.thrift:3:8:3:11 | name | +| extended.thrift:3:8:3:11 | struct User | 1 | extended.thrift:4:3:4:16 | field name | +| extended.thrift:3:8:3:11 | struct User | 2 | file://:0:0:0:0 | type_annotations | +| extended.thrift:3:8:48:15 | document | 0 | extended.thrift:3:8:3:11 | definition | +| extended.thrift:3:8:48:15 | document | 1 | extended.thrift:7:11:7:15 | definition | +| extended.thrift:3:8:48:15 | document | 2 | extended.thrift:12:9:12:16 | definition | +| extended.thrift:3:8:48:15 | document | 3 | extended.thrift:19:9:19:15 | definition | +| extended.thrift:3:8:48:15 | document | 4 | extended.thrift:21:9:21:40 | definition | +| extended.thrift:3:8:48:15 | document | 5 | extended.thrift:23:8:23:23 | definition | +| extended.thrift:3:8:48:15 | document | 6 | extended.thrift:31:6:31:12 | definition | +| extended.thrift:3:8:48:15 | document | 7 | extended.thrift:37:9:37:19 | definition | +| extended.thrift:3:8:48:15 | document | 8 | extended.thrift:46:14:46:30 | definition | +| extended.thrift:3:8:48:15 | document | 9 | extended.thrift:48:9:48:15 | definition | +| extended.thrift:3:8:48:15 | start | 0 | extended.thrift:3:8:48:15 | document | +| extended.thrift:4:3:4:3 | fieldid | 0 | extended.thrift:4:3:4:3 | INTCONSTANT 1 | +| extended.thrift:4:3:4:16 | field name | 0 | extended.thrift:4:3:4:3 | fieldid | +| extended.thrift:4:3:4:16 | field name | 2 | extended.thrift:4:6:4:11 | type string | +| extended.thrift:4:3:4:16 | field name | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:4:3:4:16 | field name | 4 | extended.thrift:4:13:4:16 | IDENTIFIER name | +| extended.thrift:4:3:4:16 | field name | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:4:3:4:16 | field name | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:4:3:4:16 | field name | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:4:6:4:11 | type string | 0 | extended.thrift:4:6:4:11 | STRING string | +| extended.thrift:4:6:4:11 | type string | 0 | extended.thrift:4:6:4:11 | type string | +| extended.thrift:7:11:7:15 | definition | 0 | extended.thrift:7:11:7:15 | exception Error | +| extended.thrift:7:11:7:15 | exception Error | 0 | extended.thrift:7:11:7:15 | name | +| extended.thrift:7:11:7:15 | exception Error | 1 | extended.thrift:8:3:8:13 | field what | +| extended.thrift:7:11:7:15 | exception Error | 2 | extended.thrift:9:3:9:15 | field why | +| extended.thrift:7:11:7:15 | name | 0 | extended.thrift:7:11:7:15 | IDENTIFIER Error | +| extended.thrift:8:3:8:3 | fieldid | 0 | extended.thrift:8:3:8:3 | INTCONSTANT 1 | +| extended.thrift:8:3:8:13 | field what | 0 | extended.thrift:8:3:8:3 | fieldid | +| extended.thrift:8:3:8:13 | field what | 2 | extended.thrift:8:6:8:8 | type i32 | +| extended.thrift:8:3:8:13 | field what | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:8:3:8:13 | field what | 4 | extended.thrift:8:10:8:13 | IDENTIFIER what | +| extended.thrift:8:3:8:13 | field what | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:8:3:8:13 | field what | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:8:3:8:13 | field what | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:8:6:8:8 | type i32 | 0 | extended.thrift:8:6:8:8 | I32 i32 | +| extended.thrift:8:6:8:8 | type i32 | 0 | extended.thrift:8:6:8:8 | type i32 | +| extended.thrift:9:3:9:3 | fieldid | 0 | extended.thrift:9:3:9:3 | INTCONSTANT 2 | +| extended.thrift:9:3:9:15 | field why | 0 | extended.thrift:9:3:9:3 | fieldid | +| extended.thrift:9:3:9:15 | field why | 2 | extended.thrift:9:6:9:11 | type string | +| extended.thrift:9:3:9:15 | field why | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:9:3:9:15 | field why | 4 | extended.thrift:9:13:9:15 | IDENTIFIER why | +| extended.thrift:9:3:9:15 | field why | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:9:3:9:15 | field why | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:9:3:9:15 | field why | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:9:6:9:11 | type string | 0 | extended.thrift:9:6:9:11 | STRING string | +| extended.thrift:9:6:9:11 | type string | 0 | extended.thrift:9:6:9:11 | type string | +| extended.thrift:12:9:12:16 | definition | 0 | extended.thrift:12:9:12:16 | service Extended | +| extended.thrift:12:9:12:16 | name | 0 | extended.thrift:12:9:12:16 | IDENTIFIER Extended | +| extended.thrift:12:9:12:16 | service Extended | 0 | extended.thrift:12:9:12:16 | name | +| extended.thrift:12:9:12:16 | service Extended | 1 | extended.thrift:14:4:14:15 | function getUser | +| extended.thrift:12:9:12:16 | service Extended | 2 | file://:0:0:0:0 | type_annotations | +| extended.thrift:14:4:14:7 | type User | 0 | extended.thrift:14:4:14:7 | IDENTIFIER User | +| extended.thrift:14:4:14:7 | type User | 0 | extended.thrift:14:4:14:7 | type User | +| extended.thrift:14:4:14:15 | function getUser | 0 | file://:0:0:0:0 | oneway | +| extended.thrift:14:4:14:15 | function getUser | 1 | extended.thrift:14:4:14:7 | type User | +| extended.thrift:14:4:14:15 | function getUser | 2 | extended.thrift:14:9:14:15 | name | +| extended.thrift:14:4:14:15 | function getUser | 3 | extended.thrift:14:17:14:24 | field id | +| extended.thrift:14:4:14:15 | function getUser | 4 | extended.thrift:14:35:14:47 | throws | +| extended.thrift:14:4:14:15 | function getUser | 5 | extended.thrift:15:5:15:47 | type_annotations | +| extended.thrift:14:9:14:15 | name | 0 | extended.thrift:14:9:14:15 | IDENTIFIER getUser | +| extended.thrift:14:17:14:17 | fieldid | 0 | extended.thrift:14:17:14:17 | INTCONSTANT 1 | +| extended.thrift:14:17:14:24 | field id | 0 | extended.thrift:14:17:14:17 | fieldid | +| extended.thrift:14:17:14:24 | field id | 2 | extended.thrift:14:19:14:21 | type i32 | +| extended.thrift:14:17:14:24 | field id | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:14:17:14:24 | field id | 4 | extended.thrift:14:23:14:24 | IDENTIFIER id | +| extended.thrift:14:17:14:24 | field id | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:14:17:14:24 | field id | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:14:17:14:24 | field id | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:14:19:14:21 | type i32 | 0 | extended.thrift:14:19:14:21 | I32 i32 | +| extended.thrift:14:19:14:21 | type i32 | 0 | extended.thrift:14:19:14:21 | type i32 | +| extended.thrift:14:35:14:35 | fieldid | 0 | extended.thrift:14:35:14:35 | INTCONSTANT 1 | +| extended.thrift:14:35:14:47 | field error | 0 | extended.thrift:14:35:14:35 | fieldid | +| extended.thrift:14:35:14:47 | field error | 2 | extended.thrift:14:37:14:41 | type Error | +| extended.thrift:14:35:14:47 | field error | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:14:35:14:47 | field error | 4 | extended.thrift:14:43:14:47 | IDENTIFIER error | +| extended.thrift:14:35:14:47 | field error | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:14:35:14:47 | field error | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:14:35:14:47 | field error | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:14:35:14:47 | throws | 0 | extended.thrift:14:35:14:47 | field error | +| extended.thrift:14:37:14:41 | type Error | 0 | extended.thrift:14:37:14:41 | IDENTIFIER Error | +| extended.thrift:15:5:15:16 | name | 0 | extended.thrift:15:5:15:16 | IDENTIFIER doggy.window | +| extended.thrift:15:5:15:25 | type_annotation | 0 | extended.thrift:15:5:15:16 | name | +| extended.thrift:15:5:15:25 | type_annotation | 1 | extended.thrift:15:20:15:25 | constvalue | +| extended.thrift:15:5:15:47 | type_annotations | 0 | extended.thrift:15:5:15:25 | type_annotation | +| extended.thrift:15:5:15:47 | type_annotations | 1 | extended.thrift:15:28:15:47 | type_annotation | +| extended.thrift:15:20:15:25 | constvalue | 0 | extended.thrift:15:20:15:25 | LITERAL "true" | +| extended.thrift:15:28:15:40 | name | 0 | extended.thrift:15:28:15:40 | IDENTIFIER doggy.howmuch | +| extended.thrift:15:28:15:47 | type_annotation | 0 | extended.thrift:15:28:15:40 | name | +| extended.thrift:15:28:15:47 | type_annotation | 1 | extended.thrift:15:44:15:47 | constvalue | +| extended.thrift:15:44:15:47 | constvalue | 0 | extended.thrift:15:44:15:47 | INTCONSTANT 1000 | +| extended.thrift:19:9:19:11 | type i32 | 0 | extended.thrift:19:9:19:11 | I32 i32 | +| extended.thrift:19:9:19:11 | type i32 | 0 | extended.thrift:19:9:19:11 | type i32 | +| extended.thrift:19:9:19:15 | definition | 0 | extended.thrift:19:9:19:15 | typedef int | +| extended.thrift:19:9:19:15 | typedef int | 0 | extended.thrift:19:9:19:11 | type i32 | +| extended.thrift:19:9:19:15 | typedef int | 1 | file://:0:0:0:0 | type_annotations | +| extended.thrift:19:9:19:15 | typedef int | 2 | extended.thrift:19:13:19:15 | name | +| extended.thrift:19:9:19:15 | typedef int | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:19:13:19:15 | name | 0 | extended.thrift:19:13:19:15 | IDENTIFIER int | +| extended.thrift:21:9:21:11 | type i64 | 0 | extended.thrift:21:9:21:11 | I64 i64 | +| extended.thrift:21:9:21:11 | type i64 | 0 | extended.thrift:21:9:21:11 | type i64 | +| extended.thrift:21:9:21:40 | definition | 0 | extended.thrift:21:9:21:40 | typedef shrubbery | +| extended.thrift:21:9:21:40 | typedef shrubbery | 0 | extended.thrift:21:9:21:11 | type i64 | +| extended.thrift:21:9:21:40 | typedef shrubbery | 1 | extended.thrift:21:13:21:29 | type_annotations | +| extended.thrift:21:9:21:40 | typedef shrubbery | 2 | extended.thrift:21:32:21:40 | name | +| extended.thrift:21:9:21:40 | typedef shrubbery | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:21:13:21:19 | name | 0 | extended.thrift:21:13:21:19 | IDENTIFIER foo.bar | +| extended.thrift:21:13:21:29 | type_annotation | 0 | extended.thrift:21:13:21:19 | name | +| extended.thrift:21:13:21:29 | type_annotation | 1 | extended.thrift:21:23:21:29 | constvalue | +| extended.thrift:21:13:21:29 | type_annotations | 0 | extended.thrift:21:13:21:29 | type_annotation | +| extended.thrift:21:23:21:29 | constvalue | 0 | extended.thrift:21:23:21:29 | LITERAL "hello" | +| extended.thrift:21:32:21:40 | name | 0 | extended.thrift:21:32:21:40 | IDENTIFIER shrubbery | +| extended.thrift:23:8:23:23 | definition | 0 | extended.thrift:23:8:23:23 | struct with_annotations | +| extended.thrift:23:8:23:23 | name | 0 | extended.thrift:23:8:23:23 | IDENTIFIER with_annotations | +| extended.thrift:23:8:23:23 | struct with_annotations | 0 | extended.thrift:23:8:23:23 | name | +| extended.thrift:23:8:23:23 | struct with_annotations | 1 | extended.thrift:25:5:25:22 | field i1 | +| extended.thrift:23:8:23:23 | struct with_annotations | 2 | extended.thrift:26:5:26:37 | field i2 | +| extended.thrift:23:8:23:23 | struct with_annotations | 3 | extended.thrift:27:5:27:20 | field nice | +| extended.thrift:23:8:23:23 | struct with_annotations | 4 | extended.thrift:29:5:29:21 | type_annotations | +| extended.thrift:25:5:25:5 | fieldid | 0 | extended.thrift:25:5:25:5 | INTCONSTANT 1 | +| extended.thrift:25:5:25:22 | field i1 | 0 | extended.thrift:25:5:25:5 | fieldid | +| extended.thrift:25:5:25:22 | field i1 | 2 | extended.thrift:25:17:25:19 | type int | +| extended.thrift:25:5:25:22 | field i1 | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:25:5:25:22 | field i1 | 4 | extended.thrift:25:21:25:22 | IDENTIFIER i1 | +| extended.thrift:25:5:25:22 | field i1 | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:25:5:25:22 | field i1 | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:25:5:25:22 | field i1 | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:25:17:25:19 | type int | 0 | extended.thrift:25:17:25:19 | IDENTIFIER int | +| extended.thrift:26:5:26:5 | fieldid | 0 | extended.thrift:26:5:26:5 | INTCONSTANT 2 | +| extended.thrift:26:5:26:37 | field i2 | 0 | extended.thrift:26:5:26:5 | fieldid | +| extended.thrift:26:5:26:37 | field i2 | 2 | extended.thrift:26:8:26:10 | type int | +| extended.thrift:26:5:26:37 | field i2 | 3 | extended.thrift:26:13:26:33 | type_annotations | +| extended.thrift:26:5:26:37 | field i2 | 4 | extended.thrift:26:36:26:37 | IDENTIFIER i2 | +| extended.thrift:26:5:26:37 | field i2 | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:26:5:26:37 | field i2 | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:26:5:26:37 | field i2 | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:26:8:26:10 | type int | 0 | extended.thrift:26:8:26:10 | IDENTIFIER int | +| extended.thrift:26:13:26:25 | name | 0 | extended.thrift:26:13:26:25 | IDENTIFIER type.annotate | +| extended.thrift:26:13:26:33 | type_annotation | 0 | extended.thrift:26:13:26:25 | name | +| extended.thrift:26:13:26:33 | type_annotation | 1 | extended.thrift:26:29:26:33 | constvalue | +| extended.thrift:26:13:26:33 | type_annotations | 0 | extended.thrift:26:13:26:33 | type_annotation | +| extended.thrift:26:29:26:33 | constvalue | 0 | extended.thrift:26:29:26:33 | LITERAL "foo" | +| extended.thrift:27:5:27:5 | fieldid | 0 | extended.thrift:27:5:27:5 | INTCONSTANT 3 | +| extended.thrift:27:5:27:20 | field nice | 0 | extended.thrift:27:5:27:5 | fieldid | +| extended.thrift:27:5:27:20 | field nice | 2 | extended.thrift:27:8:27:15 | type shubbery | +| extended.thrift:27:5:27:20 | field nice | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:27:5:27:20 | field nice | 4 | extended.thrift:27:17:27:20 | IDENTIFIER nice | +| extended.thrift:27:5:27:20 | field nice | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:27:5:27:20 | field nice | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:27:5:27:20 | field nice | 7 | extended.thrift:27:23:27:44 | type_annotations | +| extended.thrift:27:8:27:15 | type shubbery | 0 | extended.thrift:27:8:27:15 | IDENTIFIER shubbery | +| extended.thrift:27:23:27:37 | name | 0 | extended.thrift:27:23:27:37 | IDENTIFIER knights.who.say | +| extended.thrift:27:23:27:44 | type_annotation | 0 | extended.thrift:27:23:27:37 | name | +| extended.thrift:27:23:27:44 | type_annotation | 1 | extended.thrift:27:41:27:44 | constvalue | +| extended.thrift:27:23:27:44 | type_annotations | 0 | extended.thrift:27:23:27:44 | type_annotation | +| extended.thrift:27:41:27:44 | constvalue | 0 | extended.thrift:27:41:27:44 | LITERAL "ni" | +| extended.thrift:29:5:29:15 | name | 0 | extended.thrift:29:5:29:15 | IDENTIFIER struct.anno | +| extended.thrift:29:5:29:21 | type_annotation | 0 | extended.thrift:29:5:29:15 | name | +| extended.thrift:29:5:29:21 | type_annotation | 1 | extended.thrift:29:19:29:21 | constvalue | +| extended.thrift:29:5:29:21 | type_annotations | 0 | extended.thrift:29:5:29:21 | type_annotation | +| extended.thrift:29:19:29:21 | constvalue | 0 | extended.thrift:29:19:29:21 | LITERAL "y" | +| extended.thrift:31:6:31:12 | definition | 0 | extended.thrift:31:6:31:12 | enum Animals | +| extended.thrift:31:6:31:12 | enum Animals | 0 | extended.thrift:31:6:31:12 | name | +| extended.thrift:31:6:31:12 | enum Animals | 1 | extended.thrift:32:5:32:7 | enumfield cat | +| extended.thrift:31:6:31:12 | enum Animals | 2 | extended.thrift:33:5:33:9 | enumfield mouse | +| extended.thrift:31:6:31:12 | enum Animals | 3 | extended.thrift:34:5:34:7 | enumfield dog | +| extended.thrift:31:6:31:12 | enum Animals | 4 | extended.thrift:35:5:35:19 | type_annotations | +| extended.thrift:31:6:31:12 | name | 0 | extended.thrift:31:6:31:12 | IDENTIFIER Animals | +| extended.thrift:32:5:32:7 | enumfield cat | 0 | extended.thrift:32:5:32:7 | name | +| extended.thrift:32:5:32:7 | enumfield cat | 1 | extended.thrift:32:11:32:11 | enumvalue | +| extended.thrift:32:5:32:7 | enumfield cat | 2 | file://:0:0:0:0 | type_annotations | +| extended.thrift:32:5:32:7 | name | 0 | extended.thrift:32:5:32:7 | IDENTIFIER cat | +| extended.thrift:32:11:32:11 | enumvalue | 0 | extended.thrift:32:11:32:11 | INTCONSTANT 1 | +| extended.thrift:33:5:33:9 | enumfield mouse | 0 | extended.thrift:33:5:33:9 | name | +| extended.thrift:33:5:33:9 | enumfield mouse | 1 | extended.thrift:33:13:33:13 | enumvalue | +| extended.thrift:33:5:33:9 | enumfield mouse | 2 | file://:0:0:0:0 | type_annotations | +| extended.thrift:33:5:33:9 | name | 0 | extended.thrift:33:5:33:9 | IDENTIFIER mouse | +| extended.thrift:33:13:33:13 | enumvalue | 0 | extended.thrift:33:13:33:13 | INTCONSTANT 2 | +| extended.thrift:34:5:34:7 | enumfield dog | 0 | extended.thrift:34:5:34:7 | name | +| extended.thrift:34:5:34:7 | enumfield dog | 1 | file://:0:0:0:0 | enumvalue | +| extended.thrift:34:5:34:7 | enumfield dog | 2 | file://:0:0:0:0 | type_annotations | +| extended.thrift:34:5:34:7 | name | 0 | extended.thrift:34:5:34:7 | IDENTIFIER dog | +| extended.thrift:35:5:35:13 | name | 0 | extended.thrift:35:5:35:13 | IDENTIFIER enum.anno | +| extended.thrift:35:5:35:19 | type_annotation | 0 | extended.thrift:35:5:35:13 | name | +| extended.thrift:35:5:35:19 | type_annotation | 1 | extended.thrift:35:17:35:19 | constvalue | +| extended.thrift:35:5:35:19 | type_annotations | 0 | extended.thrift:35:5:35:19 | type_annotation | +| extended.thrift:35:17:35:19 | constvalue | 0 | extended.thrift:35:17:35:19 | LITERAL "x" | +| extended.thrift:37:9:37:19 | definition | 0 | extended.thrift:37:9:37:19 | service with_throws | +| extended.thrift:37:9:37:19 | name | 0 | extended.thrift:37:9:37:19 | IDENTIFIER with_throws | +| extended.thrift:37:9:37:19 | service with_throws | 0 | extended.thrift:37:9:37:19 | name | +| extended.thrift:37:9:37:19 | service with_throws | 1 | extended.thrift:39:4:39:12 | function foo | +| extended.thrift:37:9:37:19 | service with_throws | 2 | file://:0:0:0:0 | type_annotations | +| extended.thrift:39:4:39:8 | type int32 | 0 | extended.thrift:39:4:39:8 | IDENTIFIER int32 | +| extended.thrift:39:4:39:8 | type int32 | 0 | extended.thrift:39:4:39:8 | type int32 | +| extended.thrift:39:4:39:12 | function foo | 0 | file://:0:0:0:0 | oneway | +| extended.thrift:39:4:39:12 | function foo | 1 | extended.thrift:39:4:39:8 | type int32 | +| extended.thrift:39:4:39:12 | function foo | 2 | extended.thrift:39:10:39:12 | name | +| extended.thrift:39:4:39:12 | function foo | 3 | extended.thrift:39:14:39:21 | field id | +| extended.thrift:39:4:39:12 | function foo | 4 | extended.thrift:40:9:41:22 | throws | +| extended.thrift:39:4:39:12 | function foo | 5 | file://:0:0:0:0 | type_annotations | +| extended.thrift:39:10:39:12 | name | 0 | extended.thrift:39:10:39:12 | IDENTIFIER foo | +| extended.thrift:39:14:39:14 | fieldid | 0 | extended.thrift:39:14:39:14 | INTCONSTANT 1 | +| extended.thrift:39:14:39:21 | field id | 0 | extended.thrift:39:14:39:14 | fieldid | +| extended.thrift:39:14:39:21 | field id | 2 | extended.thrift:39:16:39:18 | type i32 | +| extended.thrift:39:14:39:21 | field id | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:39:14:39:21 | field id | 4 | extended.thrift:39:20:39:21 | IDENTIFIER id | +| extended.thrift:39:14:39:21 | field id | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:39:14:39:21 | field id | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:39:14:39:21 | field id | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:39:16:39:18 | type i32 | 0 | extended.thrift:39:16:39:18 | I32 i32 | +| extended.thrift:39:16:39:18 | type i32 | 0 | extended.thrift:39:16:39:18 | type i32 | +| extended.thrift:40:9:40:9 | fieldid | 0 | extended.thrift:40:9:40:9 | INTCONSTANT 1 | +| extended.thrift:40:9:40:21 | field error | 0 | extended.thrift:40:9:40:9 | fieldid | +| extended.thrift:40:9:40:21 | field error | 2 | extended.thrift:40:11:40:15 | type Error | +| extended.thrift:40:9:40:21 | field error | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:40:9:40:21 | field error | 4 | extended.thrift:40:17:40:21 | IDENTIFIER error | +| extended.thrift:40:9:40:21 | field error | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:40:9:40:21 | field error | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:40:9:40:21 | field error | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:40:9:41:22 | throws | 0 | extended.thrift:40:9:40:21 | field error | +| extended.thrift:40:9:41:22 | throws | 1 | extended.thrift:41:9:41:22 | field cause | +| extended.thrift:40:11:40:15 | type Error | 0 | extended.thrift:40:11:40:15 | IDENTIFIER Error | +| extended.thrift:41:9:41:9 | fieldid | 0 | extended.thrift:41:9:41:9 | INTCONSTANT 3 | +| extended.thrift:41:9:41:22 | field cause | 0 | extended.thrift:41:9:41:9 | fieldid | +| extended.thrift:41:9:41:22 | field cause | 2 | extended.thrift:41:11:41:16 | type string | +| extended.thrift:41:9:41:22 | field cause | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:41:9:41:22 | field cause | 4 | extended.thrift:41:18:41:22 | IDENTIFIER cause | +| extended.thrift:41:9:41:22 | field cause | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:41:9:41:22 | field cause | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:41:9:41:22 | field cause | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:41:11:41:16 | type string | 0 | extended.thrift:41:11:41:16 | STRING string | +| extended.thrift:41:11:41:16 | type string | 0 | extended.thrift:41:11:41:16 | type string | +| extended.thrift:46:14:46:22 | type shrubbery | 0 | extended.thrift:46:14:46:22 | IDENTIFIER shrubbery | +| extended.thrift:46:14:46:22 | type shrubbery | 0 | extended.thrift:46:14:46:22 | type shrubbery | +| extended.thrift:46:14:46:22 | type shrubbery | 0 | extended.thrift:46:14:46:22 | type shrubbery | +| extended.thrift:46:14:46:22 | type shrubbery | 0 | extended.thrift:46:14:46:22 | type shrubbery | +| extended.thrift:46:14:46:30 | definition | 0 | extended.thrift:46:14:46:30 | typedef border | +| extended.thrift:46:14:46:30 | typedef border | 0 | extended.thrift:46:14:46:22 | type shrubbery | +| extended.thrift:46:14:46:30 | typedef border | 1 | file://:0:0:0:0 | type_annotations | +| extended.thrift:46:14:46:30 | typedef border | 2 | extended.thrift:46:25:46:30 | name | +| extended.thrift:46:14:46:30 | typedef border | 3 | extended.thrift:46:34:46:45 | type_annotations | +| extended.thrift:46:25:46:30 | name | 0 | extended.thrift:46:25:46:30 | IDENTIFIER border | +| extended.thrift:46:34:46:35 | name | 0 | extended.thrift:46:34:46:35 | IDENTIFIER ni | +| extended.thrift:46:34:46:45 | type_annotation | 0 | extended.thrift:46:34:46:35 | name | +| extended.thrift:46:34:46:45 | type_annotation | 1 | extended.thrift:46:39:46:45 | constvalue | +| extended.thrift:46:34:46:45 | type_annotations | 0 | extended.thrift:46:34:46:45 | type_annotation | +| extended.thrift:46:39:46:45 | constvalue | 0 | extended.thrift:46:39:46:45 | LITERAL "false" | +| extended.thrift:48:9:48:15 | definition | 0 | extended.thrift:48:9:48:15 | service TheShop | +| extended.thrift:48:9:48:15 | name | 0 | extended.thrift:48:9:48:15 | IDENTIFIER TheShop | +| extended.thrift:48:9:48:15 | service TheShop | 0 | extended.thrift:48:9:48:15 | name | +| extended.thrift:48:9:48:15 | service TheShop | 1 | extended.thrift:50:5:50:18 | function getPet | +| extended.thrift:48:9:48:15 | service TheShop | 2 | extended.thrift:59:5:59:32 | type_annotations | +| extended.thrift:50:5:50:11 | type Animals | 0 | extended.thrift:50:5:50:11 | IDENTIFIER Animals | +| extended.thrift:50:5:50:11 | type Animals | 0 | extended.thrift:50:5:50:11 | type Animals | +| extended.thrift:50:5:50:18 | function getPet | 0 | file://:0:0:0:0 | oneway | +| extended.thrift:50:5:50:18 | function getPet | 1 | extended.thrift:50:5:50:11 | type Animals | +| extended.thrift:50:5:50:18 | function getPet | 2 | extended.thrift:50:13:50:18 | name | +| extended.thrift:50:5:50:18 | function getPet | 3 | extended.thrift:51:9:51:30 | field owner | +| extended.thrift:50:5:50:18 | function getPet | 4 | extended.thrift:53:9:56:21 | throws | +| extended.thrift:50:5:50:18 | function getPet | 5 | file://:0:0:0:0 | type_annotations | +| extended.thrift:50:13:50:18 | name | 0 | extended.thrift:50:13:50:18 | IDENTIFIER getPet | +| extended.thrift:51:9:51:9 | fieldid | 0 | extended.thrift:51:9:51:9 | INTCONSTANT 1 | +| extended.thrift:51:9:51:30 | field owner | 0 | extended.thrift:51:9:51:9 | fieldid | +| extended.thrift:51:9:51:30 | field owner | 2 | extended.thrift:51:21:51:24 | type User | +| extended.thrift:51:9:51:30 | field owner | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:51:9:51:30 | field owner | 4 | extended.thrift:51:26:51:30 | IDENTIFIER owner | +| extended.thrift:51:9:51:30 | field owner | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:51:9:51:30 | field owner | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:51:9:51:30 | field owner | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:51:21:51:24 | type User | 0 | extended.thrift:51:21:51:24 | IDENTIFIER User | +| extended.thrift:53:9:53:9 | fieldid | 0 | extended.thrift:53:9:53:9 | INTCONSTANT 1 | +| extended.thrift:53:9:53:24 | field napping | 0 | extended.thrift:53:9:53:9 | fieldid | +| extended.thrift:53:9:53:24 | field napping | 2 | extended.thrift:53:12:53:16 | type Error | +| extended.thrift:53:9:53:24 | field napping | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:53:9:53:24 | field napping | 4 | extended.thrift:53:18:53:24 | IDENTIFIER napping | +| extended.thrift:53:9:53:24 | field napping | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:53:9:53:24 | field napping | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:53:9:53:24 | field napping | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:53:9:56:21 | throws | 0 | extended.thrift:53:9:53:24 | field napping | +| extended.thrift:53:9:56:21 | throws | 1 | extended.thrift:54:9:54:30 | field pining | +| extended.thrift:53:9:56:21 | throws | 2 | extended.thrift:55:9:55:35 | field resting | +| extended.thrift:53:9:56:21 | throws | 3 | extended.thrift:56:9:56:21 | field deaf | +| extended.thrift:53:12:53:16 | type Error | 0 | extended.thrift:53:12:53:16 | IDENTIFIER Error | +| extended.thrift:54:9:54:9 | fieldid | 0 | extended.thrift:54:9:54:9 | INTCONSTANT 2 | +| extended.thrift:54:9:54:30 | field pining | 0 | extended.thrift:54:9:54:9 | fieldid | +| extended.thrift:54:9:54:30 | field pining | 2 | extended.thrift:54:12:54:23 | type AnotherError | +| extended.thrift:54:9:54:30 | field pining | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:54:9:54:30 | field pining | 4 | extended.thrift:54:25:54:30 | IDENTIFIER pining | +| extended.thrift:54:9:54:30 | field pining | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:54:9:54:30 | field pining | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:54:9:54:30 | field pining | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:54:12:54:23 | type AnotherError | 0 | extended.thrift:54:12:54:23 | IDENTIFIER AnotherError | +| extended.thrift:55:9:55:9 | fieldid | 0 | extended.thrift:55:9:55:9 | INTCONSTANT 3 | +| extended.thrift:55:9:55:35 | field resting | 0 | extended.thrift:55:9:55:9 | fieldid | +| extended.thrift:55:9:55:35 | field resting | 2 | extended.thrift:55:12:55:27 | type ThirdKindOfError | +| extended.thrift:55:9:55:35 | field resting | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:55:9:55:35 | field resting | 4 | extended.thrift:55:29:55:35 | IDENTIFIER resting | +| extended.thrift:55:9:55:35 | field resting | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:55:9:55:35 | field resting | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:55:9:55:35 | field resting | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:55:12:55:27 | type ThirdKindOfError | 0 | extended.thrift:55:12:55:27 | IDENTIFIER ThirdKindOfError | +| extended.thrift:56:9:56:9 | fieldid | 0 | extended.thrift:56:9:56:9 | INTCONSTANT 4 | +| extended.thrift:56:9:56:21 | field deaf | 0 | extended.thrift:56:9:56:9 | fieldid | +| extended.thrift:56:9:56:21 | field deaf | 2 | extended.thrift:56:12:56:16 | type Error | +| extended.thrift:56:9:56:21 | field deaf | 3 | file://:0:0:0:0 | type_annotations | +| extended.thrift:56:9:56:21 | field deaf | 4 | extended.thrift:56:18:56:21 | IDENTIFIER deaf | +| extended.thrift:56:9:56:21 | field deaf | 5 | file://:0:0:0:0 | fieldvalue | +| extended.thrift:56:9:56:21 | field deaf | 6 | file://:0:0:0:0 | xsdfieldoptions | +| extended.thrift:56:9:56:21 | field deaf | 7 | file://:0:0:0:0 | type_annotations | +| extended.thrift:56:12:56:16 | type Error | 0 | extended.thrift:56:12:56:16 | IDENTIFIER Error | +| extended.thrift:59:5:59:22 | name | 0 | extended.thrift:59:5:59:22 | IDENTIFIER service.annotation | +| extended.thrift:59:5:59:32 | type_annotation | 0 | extended.thrift:59:5:59:22 | name | +| extended.thrift:59:5:59:32 | type_annotation | 1 | extended.thrift:59:26:59:32 | constvalue | +| extended.thrift:59:5:59:32 | type_annotations | 0 | extended.thrift:59:5:59:32 | type_annotation | +| extended.thrift:59:26:59:32 | constvalue | 0 | extended.thrift:59:26:59:32 | LITERAL "thing" | +| test.thrift:3:9:3:23 | header | 0 | test.thrift:3:9:3:23 | include | +| test.thrift:3:9:3:23 | include | 0 | test.thrift:3:9:3:23 | LITERAL "shared.thrift" | +| test.thrift:3:9:83:17 | document | 0 | test.thrift:3:9:3:23 | header | +| test.thrift:3:9:83:17 | document | 1 | file://:0:0:0:0 | header | +| test.thrift:3:9:83:17 | document | 2 | file://:0:0:0:0 | header | +| test.thrift:3:9:83:17 | document | 3 | file://:0:0:0:0 | header | +| test.thrift:3:9:83:17 | document | 4 | file://:0:0:0:0 | header | +| test.thrift:3:9:83:17 | document | 5 | file://:0:0:0:0 | header | +| test.thrift:3:9:83:17 | document | 6 | test.thrift:15:7:15:30 | definition | +| test.thrift:3:9:83:17 | document | 7 | test.thrift:16:11:16:75 | definition | +| test.thrift:3:9:83:17 | document | 8 | test.thrift:22:6:22:14 | definition | +| test.thrift:3:9:83:17 | document | 9 | test.thrift:38:8:38:11 | definition | +| test.thrift:3:9:83:17 | document | 10 | test.thrift:48:11:48:26 | definition | +| test.thrift:3:9:83:17 | document | 11 | test.thrift:57:9:57:18 | definition | +| test.thrift:3:9:83:17 | document | 12 | test.thrift:83:9:83:17 | definition | +| test.thrift:3:9:83:17 | start | 0 | test.thrift:3:9:83:17 | document | +| test.thrift:15:7:15:9 | type i32 | 0 | test.thrift:15:7:15:9 | I32 i32 | +| test.thrift:15:7:15:9 | type i32 | 0 | test.thrift:15:7:15:9 | type i32 | +| test.thrift:15:7:15:30 | const | 0 | test.thrift:15:7:15:9 | type i32 | +| test.thrift:15:7:15:30 | const | 1 | test.thrift:15:11:15:23 | name | +| test.thrift:15:7:15:30 | const | 2 | test.thrift:15:27:15:30 | constvalue | +| test.thrift:15:7:15:30 | definition | 0 | test.thrift:15:7:15:30 | const | +| test.thrift:15:11:15:23 | name | 0 | test.thrift:15:11:15:23 | IDENTIFIER INT32CONSTANT | +| test.thrift:15:27:15:30 | constvalue | 0 | test.thrift:15:27:15:30 | INTCONSTANT 9853 | +| test.thrift:16:11:16:16 | type string | 0 | test.thrift:16:11:16:16 | STRING string | +| test.thrift:16:11:16:16 | type string | 0 | test.thrift:16:11:16:16 | type string | +| test.thrift:16:11:16:16 | type string | 0 | test.thrift:16:11:16:16 | type string | +| test.thrift:16:11:16:16 | type string | 0 | test.thrift:16:11:16:16 | type string | +| test.thrift:16:11:16:16 | type string | 0 | test.thrift:16:11:16:16 | type string | +| test.thrift:16:11:16:16 | type string | 1 | test.thrift:16:18:16:23 | type string | +| test.thrift:16:11:16:75 | const | 0 | test.thrift:16:11:16:16 | type string | +| test.thrift:16:11:16:75 | const | 1 | test.thrift:16:26:16:36 | name | +| test.thrift:16:11:16:75 | const | 2 | test.thrift:16:41:16:75 | constvalue | +| test.thrift:16:11:16:75 | definition | 0 | test.thrift:16:11:16:75 | const | +| test.thrift:16:18:16:23 | type string | 0 | test.thrift:16:18:16:23 | STRING string | +| test.thrift:16:18:16:23 | type string | 0 | test.thrift:16:18:16:23 | type string | +| test.thrift:16:26:16:36 | name | 0 | test.thrift:16:26:16:36 | IDENTIFIER MAPCONSTANT | +| test.thrift:16:41:16:47 | constvalue | 0 | test.thrift:16:41:16:47 | LITERAL 'hello' | +| test.thrift:16:41:16:55 | constmapelt | 0 | test.thrift:16:41:16:47 | constvalue | +| test.thrift:16:41:16:55 | constmapelt | 1 | test.thrift:16:49:16:55 | constvalue | +| test.thrift:16:41:16:75 | constmap | 0 | test.thrift:16:41:16:55 | constmapelt | +| test.thrift:16:41:16:75 | constmap | 1 | test.thrift:16:58:16:75 | constmapelt | +| test.thrift:16:41:16:75 | constvalue | 0 | test.thrift:16:41:16:75 | constmap | +| test.thrift:16:49:16:55 | constvalue | 0 | test.thrift:16:49:16:55 | LITERAL 'world' | +| test.thrift:16:58:16:68 | constvalue | 0 | test.thrift:16:58:16:68 | LITERAL 'goodnight' | +| test.thrift:16:58:16:75 | constmapelt | 0 | test.thrift:16:58:16:68 | constvalue | +| test.thrift:16:58:16:75 | constmapelt | 1 | test.thrift:16:70:16:75 | constvalue | +| test.thrift:16:70:16:75 | constvalue | 0 | test.thrift:16:70:16:75 | LITERAL 'moon' | +| test.thrift:22:6:22:14 | definition | 0 | test.thrift:22:6:22:14 | enum Operation | +| test.thrift:22:6:22:14 | enum Operation | 0 | test.thrift:22:6:22:14 | name | +| test.thrift:22:6:22:14 | enum Operation | 1 | test.thrift:23:3:23:5 | enumfield ADD | +| test.thrift:22:6:22:14 | enum Operation | 2 | test.thrift:24:3:24:10 | enumfield SUBTRACT | +| test.thrift:22:6:22:14 | enum Operation | 3 | test.thrift:25:3:25:10 | enumfield MULTIPLY | +| test.thrift:22:6:22:14 | enum Operation | 4 | test.thrift:26:3:26:8 | enumfield DIVIDE | +| test.thrift:22:6:22:14 | enum Operation | 5 | file://:0:0:0:0 | type_annotations | +| test.thrift:22:6:22:14 | name | 0 | test.thrift:22:6:22:14 | IDENTIFIER Operation | +| test.thrift:23:3:23:5 | enumfield ADD | 0 | test.thrift:23:3:23:5 | name | +| test.thrift:23:3:23:5 | enumfield ADD | 1 | test.thrift:23:9:23:9 | enumvalue | +| test.thrift:23:3:23:5 | enumfield ADD | 2 | file://:0:0:0:0 | type_annotations | +| test.thrift:23:3:23:5 | name | 0 | test.thrift:23:3:23:5 | IDENTIFIER ADD | +| test.thrift:23:9:23:9 | enumvalue | 0 | test.thrift:23:9:23:9 | INTCONSTANT 1 | +| test.thrift:24:3:24:10 | enumfield SUBTRACT | 0 | test.thrift:24:3:24:10 | name | +| test.thrift:24:3:24:10 | enumfield SUBTRACT | 1 | test.thrift:24:14:24:14 | enumvalue | +| test.thrift:24:3:24:10 | enumfield SUBTRACT | 2 | file://:0:0:0:0 | type_annotations | +| test.thrift:24:3:24:10 | name | 0 | test.thrift:24:3:24:10 | IDENTIFIER SUBTRACT | +| test.thrift:24:14:24:14 | enumvalue | 0 | test.thrift:24:14:24:14 | INTCONSTANT 2 | +| test.thrift:25:3:25:10 | enumfield MULTIPLY | 0 | test.thrift:25:3:25:10 | name | +| test.thrift:25:3:25:10 | enumfield MULTIPLY | 1 | test.thrift:25:14:25:14 | enumvalue | +| test.thrift:25:3:25:10 | enumfield MULTIPLY | 2 | file://:0:0:0:0 | type_annotations | +| test.thrift:25:3:25:10 | name | 0 | test.thrift:25:3:25:10 | IDENTIFIER MULTIPLY | +| test.thrift:25:14:25:14 | enumvalue | 0 | test.thrift:25:14:25:14 | INTCONSTANT 3 | +| test.thrift:26:3:26:8 | enumfield DIVIDE | 0 | test.thrift:26:3:26:8 | name | +| test.thrift:26:3:26:8 | enumfield DIVIDE | 1 | test.thrift:26:12:26:12 | enumvalue | +| test.thrift:26:3:26:8 | enumfield DIVIDE | 2 | file://:0:0:0:0 | type_annotations | +| test.thrift:26:3:26:8 | name | 0 | test.thrift:26:3:26:8 | IDENTIFIER DIVIDE | +| test.thrift:26:12:26:12 | enumvalue | 0 | test.thrift:26:12:26:12 | INTCONSTANT 4 | +| test.thrift:38:8:38:11 | definition | 0 | test.thrift:38:8:38:11 | struct Work | +| test.thrift:38:8:38:11 | name | 0 | test.thrift:38:8:38:11 | IDENTIFIER Work | +| test.thrift:38:8:38:11 | struct Work | 0 | test.thrift:38:8:38:11 | name | +| test.thrift:38:8:38:11 | struct Work | 1 | test.thrift:39:3:39:13 | field num1 | +| test.thrift:38:8:38:11 | struct Work | 2 | test.thrift:40:3:40:13 | field num2 | +| test.thrift:38:8:38:11 | struct Work | 3 | test.thrift:41:3:41:17 | field op | +| test.thrift:38:8:38:11 | struct Work | 4 | test.thrift:42:3:42:28 | field comment | +| test.thrift:38:8:38:11 | struct Work | 5 | file://:0:0:0:0 | type_annotations | +| test.thrift:39:3:39:3 | fieldid | 0 | test.thrift:39:3:39:3 | INTCONSTANT 1 | +| test.thrift:39:3:39:13 | field num1 | 0 | test.thrift:39:3:39:3 | fieldid | +| test.thrift:39:3:39:13 | field num1 | 2 | test.thrift:39:6:39:8 | type i32 | +| test.thrift:39:3:39:13 | field num1 | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:39:3:39:13 | field num1 | 4 | test.thrift:39:10:39:13 | IDENTIFIER num1 | +| test.thrift:39:3:39:13 | field num1 | 5 | test.thrift:39:17:39:17 | fieldvalue | +| test.thrift:39:3:39:13 | field num1 | 6 | file://:0:0:0:0 | xsdfieldoptions | +| test.thrift:39:3:39:13 | field num1 | 7 | file://:0:0:0:0 | type_annotations | +| test.thrift:39:6:39:8 | type i32 | 0 | test.thrift:39:6:39:8 | I32 i32 | +| test.thrift:39:6:39:8 | type i32 | 0 | test.thrift:39:6:39:8 | type i32 | +| test.thrift:39:17:39:17 | constvalue | 0 | test.thrift:39:17:39:17 | INTCONSTANT 0 | +| test.thrift:39:17:39:17 | fieldvalue | 0 | test.thrift:39:17:39:17 | constvalue | +| test.thrift:40:3:40:3 | fieldid | 0 | test.thrift:40:3:40:3 | INTCONSTANT 2 | +| test.thrift:40:3:40:13 | field num2 | 0 | test.thrift:40:3:40:3 | fieldid | +| test.thrift:40:3:40:13 | field num2 | 2 | test.thrift:40:6:40:8 | type i32 | +| test.thrift:40:3:40:13 | field num2 | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:40:3:40:13 | field num2 | 4 | test.thrift:40:10:40:13 | IDENTIFIER num2 | +| test.thrift:40:3:40:13 | field num2 | 5 | file://:0:0:0:0 | fieldvalue | +| test.thrift:40:3:40:13 | field num2 | 6 | file://:0:0:0:0 | xsdfieldoptions | +| test.thrift:40:3:40:13 | field num2 | 7 | file://:0:0:0:0 | type_annotations | +| test.thrift:40:6:40:8 | type i32 | 0 | test.thrift:40:6:40:8 | I32 i32 | +| test.thrift:40:6:40:8 | type i32 | 0 | test.thrift:40:6:40:8 | type i32 | +| test.thrift:41:3:41:3 | fieldid | 0 | test.thrift:41:3:41:3 | INTCONSTANT 3 | +| test.thrift:41:3:41:17 | field op | 0 | test.thrift:41:3:41:3 | fieldid | +| test.thrift:41:3:41:17 | field op | 2 | test.thrift:41:6:41:14 | type Operation | +| test.thrift:41:3:41:17 | field op | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:41:3:41:17 | field op | 4 | test.thrift:41:16:41:17 | IDENTIFIER op | +| test.thrift:41:3:41:17 | field op | 5 | file://:0:0:0:0 | fieldvalue | +| test.thrift:41:3:41:17 | field op | 6 | file://:0:0:0:0 | xsdfieldoptions | +| test.thrift:41:3:41:17 | field op | 7 | file://:0:0:0:0 | type_annotations | +| test.thrift:41:6:41:14 | type Operation | 0 | test.thrift:41:6:41:14 | IDENTIFIER Operation | +| test.thrift:42:3:42:3 | fieldid | 0 | test.thrift:42:3:42:3 | INTCONSTANT 4 | +| test.thrift:42:3:42:28 | field comment | 0 | test.thrift:42:3:42:3 | fieldid | +| test.thrift:42:3:42:28 | field comment | 2 | test.thrift:42:15:42:20 | type string | +| test.thrift:42:3:42:28 | field comment | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:42:3:42:28 | field comment | 4 | test.thrift:42:22:42:28 | IDENTIFIER comment | +| test.thrift:42:3:42:28 | field comment | 5 | file://:0:0:0:0 | fieldvalue | +| test.thrift:42:3:42:28 | field comment | 6 | file://:0:0:0:0 | xsdfieldoptions | +| test.thrift:42:3:42:28 | field comment | 7 | file://:0:0:0:0 | type_annotations | +| test.thrift:42:15:42:20 | type string | 0 | test.thrift:42:15:42:20 | STRING string | +| test.thrift:42:15:42:20 | type string | 0 | test.thrift:42:15:42:20 | type string | +| test.thrift:48:11:48:26 | definition | 0 | test.thrift:48:11:48:26 | exception InvalidOperation | +| test.thrift:48:11:48:26 | exception InvalidOperation | 0 | test.thrift:48:11:48:26 | name | +| test.thrift:48:11:48:26 | exception InvalidOperation | 1 | test.thrift:49:3:49:13 | field what | +| test.thrift:48:11:48:26 | exception InvalidOperation | 2 | test.thrift:50:3:50:15 | field why | +| test.thrift:48:11:48:26 | name | 0 | test.thrift:48:11:48:26 | IDENTIFIER InvalidOperation | +| test.thrift:49:3:49:3 | fieldid | 0 | test.thrift:49:3:49:3 | INTCONSTANT 1 | +| test.thrift:49:3:49:13 | field what | 0 | test.thrift:49:3:49:3 | fieldid | +| test.thrift:49:3:49:13 | field what | 2 | test.thrift:49:6:49:8 | type i32 | +| test.thrift:49:3:49:13 | field what | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:49:3:49:13 | field what | 4 | test.thrift:49:10:49:13 | IDENTIFIER what | +| test.thrift:49:3:49:13 | field what | 5 | file://:0:0:0:0 | fieldvalue | +| test.thrift:49:3:49:13 | field what | 6 | file://:0:0:0:0 | xsdfieldoptions | +| test.thrift:49:3:49:13 | field what | 7 | file://:0:0:0:0 | type_annotations | +| test.thrift:49:6:49:8 | type i32 | 0 | test.thrift:49:6:49:8 | I32 i32 | +| test.thrift:49:6:49:8 | type i32 | 0 | test.thrift:49:6:49:8 | type i32 | +| test.thrift:50:3:50:3 | fieldid | 0 | test.thrift:50:3:50:3 | INTCONSTANT 2 | +| test.thrift:50:3:50:15 | field why | 0 | test.thrift:50:3:50:3 | fieldid | +| test.thrift:50:3:50:15 | field why | 2 | test.thrift:50:6:50:11 | type string | +| test.thrift:50:3:50:15 | field why | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:50:3:50:15 | field why | 4 | test.thrift:50:13:50:15 | IDENTIFIER why | +| test.thrift:50:3:50:15 | field why | 5 | file://:0:0:0:0 | fieldvalue | +| test.thrift:50:3:50:15 | field why | 6 | file://:0:0:0:0 | xsdfieldoptions | +| test.thrift:50:3:50:15 | field why | 7 | file://:0:0:0:0 | type_annotations | +| test.thrift:50:6:50:11 | type string | 0 | test.thrift:50:6:50:11 | STRING string | +| test.thrift:50:6:50:11 | type string | 0 | test.thrift:50:6:50:11 | type string | +| test.thrift:57:9:57:18 | definition | 0 | test.thrift:57:9:57:18 | service Calculator | +| test.thrift:57:9:57:18 | name | 0 | test.thrift:57:9:57:18 | IDENTIFIER Calculator | +| test.thrift:57:9:57:18 | service Calculator | 0 | test.thrift:57:9:57:18 | name | +| test.thrift:57:9:57:18 | service Calculator | 1 | test.thrift:57:28:57:47 | extends | +| test.thrift:57:9:57:18 | service Calculator | 2 | test.thrift:66:4:66:12 | function ping | +| test.thrift:57:9:57:18 | service Calculator | 3 | test.thrift:68:4:68:10 | function add | +| test.thrift:57:9:57:18 | service Calculator | 4 | test.thrift:70:4:70:16 | function calculate | +| test.thrift:57:9:57:18 | service Calculator | 5 | test.thrift:77:11:77:18 | function zip | +| test.thrift:57:9:57:18 | service Calculator | 6 | file://:0:0:0:0 | type_annotations | +| test.thrift:57:28:57:47 | extends | 0 | test.thrift:57:28:57:47 | IDENTIFIER shared.SharedService | +| test.thrift:66:4:66:7 | type void | 0 | test.thrift:66:4:66:7 | VOID void | +| test.thrift:66:4:66:12 | function ping | 0 | file://:0:0:0:0 | oneway | +| test.thrift:66:4:66:12 | function ping | 1 | test.thrift:66:4:66:7 | type void | +| test.thrift:66:4:66:12 | function ping | 2 | test.thrift:66:9:66:12 | name | +| test.thrift:66:4:66:12 | function ping | 3 | file://:0:0:0:0 | throws | +| test.thrift:66:4:66:12 | function ping | 4 | file://:0:0:0:0 | type_annotations | +| test.thrift:66:9:66:12 | name | 0 | test.thrift:66:9:66:12 | IDENTIFIER ping | +| test.thrift:68:4:68:6 | type i32 | 0 | test.thrift:68:4:68:6 | I32 i32 | +| test.thrift:68:4:68:6 | type i32 | 0 | test.thrift:68:4:68:6 | type i32 | +| test.thrift:68:4:68:6 | type i32 | 0 | test.thrift:68:4:68:6 | type i32 | +| test.thrift:68:4:68:10 | function add | 0 | file://:0:0:0:0 | oneway | +| test.thrift:68:4:68:10 | function add | 1 | test.thrift:68:4:68:6 | type i32 | +| test.thrift:68:4:68:10 | function add | 2 | test.thrift:68:8:68:10 | name | +| test.thrift:68:4:68:10 | function add | 3 | test.thrift:68:12:68:21 | field num1 | +| test.thrift:68:4:68:10 | function add | 4 | test.thrift:68:24:68:33 | field num2 | +| test.thrift:68:4:68:10 | function add | 5 | file://:0:0:0:0 | throws | +| test.thrift:68:4:68:10 | function add | 6 | file://:0:0:0:0 | type_annotations | +| test.thrift:68:8:68:10 | name | 0 | test.thrift:68:8:68:10 | IDENTIFIER add | +| test.thrift:68:12:68:12 | fieldid | 0 | test.thrift:68:12:68:12 | INTCONSTANT 1 | +| test.thrift:68:12:68:21 | field num1 | 0 | test.thrift:68:12:68:12 | fieldid | +| test.thrift:68:12:68:21 | field num1 | 2 | test.thrift:68:14:68:16 | type i32 | +| test.thrift:68:12:68:21 | field num1 | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:68:12:68:21 | field num1 | 4 | test.thrift:68:18:68:21 | IDENTIFIER num1 | +| test.thrift:68:12:68:21 | field num1 | 5 | file://:0:0:0:0 | fieldvalue | +| test.thrift:68:12:68:21 | field num1 | 6 | file://:0:0:0:0 | xsdfieldoptions | +| test.thrift:68:12:68:21 | field num1 | 7 | file://:0:0:0:0 | type_annotations | +| test.thrift:68:14:68:16 | type i32 | 0 | test.thrift:68:14:68:16 | I32 i32 | +| test.thrift:68:14:68:16 | type i32 | 0 | test.thrift:68:14:68:16 | type i32 | +| test.thrift:68:24:68:24 | fieldid | 0 | test.thrift:68:24:68:24 | INTCONSTANT 2 | +| test.thrift:68:24:68:33 | field num2 | 0 | test.thrift:68:24:68:24 | fieldid | +| test.thrift:68:24:68:33 | field num2 | 2 | test.thrift:68:26:68:28 | type i32 | +| test.thrift:68:24:68:33 | field num2 | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:68:24:68:33 | field num2 | 4 | test.thrift:68:30:68:33 | IDENTIFIER num2 | +| test.thrift:68:24:68:33 | field num2 | 5 | file://:0:0:0:0 | fieldvalue | +| test.thrift:68:24:68:33 | field num2 | 6 | file://:0:0:0:0 | xsdfieldoptions | +| test.thrift:68:24:68:33 | field num2 | 7 | file://:0:0:0:0 | type_annotations | +| test.thrift:68:26:68:28 | type i32 | 0 | test.thrift:68:26:68:28 | I32 i32 | +| test.thrift:68:26:68:28 | type i32 | 0 | test.thrift:68:26:68:28 | type i32 | +| test.thrift:70:4:70:6 | type i32 | 0 | test.thrift:70:4:70:6 | I32 i32 | +| test.thrift:70:4:70:6 | type i32 | 0 | test.thrift:70:4:70:6 | type i32 | +| test.thrift:70:4:70:6 | type i32 | 0 | test.thrift:70:4:70:6 | type i32 | +| test.thrift:70:4:70:16 | function calculate | 0 | file://:0:0:0:0 | oneway | +| test.thrift:70:4:70:16 | function calculate | 1 | test.thrift:70:4:70:6 | type i32 | +| test.thrift:70:4:70:16 | function calculate | 2 | test.thrift:70:8:70:16 | name | +| test.thrift:70:4:70:16 | function calculate | 3 | test.thrift:70:18:70:28 | field logid | +| test.thrift:70:4:70:16 | function calculate | 4 | test.thrift:70:31:70:38 | field w | +| test.thrift:70:4:70:16 | function calculate | 5 | test.thrift:70:49:70:71 | throws | +| test.thrift:70:4:70:16 | function calculate | 6 | file://:0:0:0:0 | type_annotations | +| test.thrift:70:8:70:16 | name | 0 | test.thrift:70:8:70:16 | IDENTIFIER calculate | +| test.thrift:70:18:70:18 | fieldid | 0 | test.thrift:70:18:70:18 | INTCONSTANT 1 | +| test.thrift:70:18:70:28 | field logid | 0 | test.thrift:70:18:70:18 | fieldid | +| test.thrift:70:18:70:28 | field logid | 2 | test.thrift:70:20:70:22 | type i32 | +| test.thrift:70:18:70:28 | field logid | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:70:18:70:28 | field logid | 4 | test.thrift:70:24:70:28 | IDENTIFIER logid | +| test.thrift:70:18:70:28 | field logid | 5 | file://:0:0:0:0 | fieldvalue | +| test.thrift:70:18:70:28 | field logid | 6 | file://:0:0:0:0 | xsdfieldoptions | +| test.thrift:70:18:70:28 | field logid | 7 | file://:0:0:0:0 | type_annotations | +| test.thrift:70:20:70:22 | type i32 | 0 | test.thrift:70:20:70:22 | I32 i32 | +| test.thrift:70:20:70:22 | type i32 | 0 | test.thrift:70:20:70:22 | type i32 | +| test.thrift:70:31:70:31 | fieldid | 0 | test.thrift:70:31:70:31 | INTCONSTANT 2 | +| test.thrift:70:31:70:38 | field w | 0 | test.thrift:70:31:70:31 | fieldid | +| test.thrift:70:31:70:38 | field w | 2 | test.thrift:70:33:70:36 | type Work | +| test.thrift:70:31:70:38 | field w | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:70:31:70:38 | field w | 4 | test.thrift:70:38:70:38 | IDENTIFIER w | +| test.thrift:70:31:70:38 | field w | 5 | file://:0:0:0:0 | fieldvalue | +| test.thrift:70:31:70:38 | field w | 6 | file://:0:0:0:0 | xsdfieldoptions | +| test.thrift:70:31:70:38 | field w | 7 | file://:0:0:0:0 | type_annotations | +| test.thrift:70:33:70:36 | type Work | 0 | test.thrift:70:33:70:36 | IDENTIFIER Work | +| test.thrift:70:49:70:49 | fieldid | 0 | test.thrift:70:49:70:49 | INTCONSTANT 1 | +| test.thrift:70:49:70:71 | field ouch | 0 | test.thrift:70:49:70:49 | fieldid | +| test.thrift:70:49:70:71 | field ouch | 2 | test.thrift:70:51:70:66 | type InvalidOperation | +| test.thrift:70:49:70:71 | field ouch | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:70:49:70:71 | field ouch | 4 | test.thrift:70:68:70:71 | IDENTIFIER ouch | +| test.thrift:70:49:70:71 | field ouch | 5 | file://:0:0:0:0 | fieldvalue | +| test.thrift:70:49:70:71 | field ouch | 6 | file://:0:0:0:0 | xsdfieldoptions | +| test.thrift:70:49:70:71 | field ouch | 7 | file://:0:0:0:0 | type_annotations | +| test.thrift:70:49:70:71 | throws | 0 | test.thrift:70:49:70:71 | field ouch | +| test.thrift:70:51:70:66 | type InvalidOperation | 0 | test.thrift:70:51:70:66 | IDENTIFIER InvalidOperation | +| test.thrift:77:11:77:14 | type void | 0 | test.thrift:77:11:77:14 | VOID void | +| test.thrift:77:11:77:18 | function zip | 0 | file://:0:0:0:0 | oneway | +| test.thrift:77:11:77:18 | function zip | 1 | test.thrift:77:11:77:14 | type void | +| test.thrift:77:11:77:18 | function zip | 2 | test.thrift:77:16:77:18 | name | +| test.thrift:77:11:77:18 | function zip | 3 | file://:0:0:0:0 | throws | +| test.thrift:77:11:77:18 | function zip | 4 | file://:0:0:0:0 | type_annotations | +| test.thrift:77:16:77:18 | name | 0 | test.thrift:77:16:77:18 | IDENTIFIER zip | +| test.thrift:83:9:83:12 | type wood | 0 | test.thrift:83:9:83:12 | IDENTIFIER wood | +| test.thrift:83:9:83:17 | definition | 0 | test.thrift:83:9:83:17 | typedef duck | +| test.thrift:83:9:83:17 | typedef duck | 0 | test.thrift:83:9:83:12 | type wood | +| test.thrift:83:9:83:17 | typedef duck | 1 | file://:0:0:0:0 | type_annotations | +| test.thrift:83:9:83:17 | typedef duck | 2 | test.thrift:83:14:83:17 | name | +| test.thrift:83:9:83:17 | typedef duck | 3 | file://:0:0:0:0 | type_annotations | +| test.thrift:83:14:83:17 | name | 0 | test.thrift:83:14:83:17 | IDENTIFIER duck | diff --git a/python/ql/test/library-tests/thrift/Child.ql b/python/ql/test/library-tests/thrift/Child.ql new file mode 100644 index 000000000000..5645c53ddb0f --- /dev/null +++ b/python/ql/test/library-tests/thrift/Child.ql @@ -0,0 +1,5 @@ + +import external.Thrift + +from ThriftElement t, int n +select t, n, t.getChild(n) diff --git a/python/ql/test/library-tests/thrift/File.expected b/python/ql/test/library-tests/thrift/File.expected new file mode 100644 index 000000000000..501035c79c40 --- /dev/null +++ b/python/ql/test/library-tests/thrift/File.expected @@ -0,0 +1,63 @@ +| ADD | test.thrift | +| Animals | extended.thrift | +| AnotherError | extended.thrift | +| Calculator | test.thrift | +| DIVIDE | test.thrift | +| Error | extended.thrift | +| Extended | extended.thrift | +| InvalidOperation | test.thrift | +| MULTIPLY | test.thrift | +| Operation | test.thrift | +| SUBTRACT | test.thrift | +| TheShop | extended.thrift | +| ThirdKindOfError | extended.thrift | +| User | extended.thrift | +| Work | test.thrift | +| add | test.thrift | +| border | extended.thrift | +| calculate | test.thrift | +| cat | extended.thrift | +| cause | extended.thrift | +| comment | test.thrift | +| deaf | extended.thrift | +| dog | extended.thrift | +| duck | test.thrift | +| error | extended.thrift | +| foo | extended.thrift | +| getPet | extended.thrift | +| getUser | extended.thrift | +| i1 | extended.thrift | +| i2 | extended.thrift | +| i32 | extended.thrift | +| i32 | test.thrift | +| i64 | extended.thrift | +| id | extended.thrift | +| int | extended.thrift | +| int32 | extended.thrift | +| logid | test.thrift | +| mouse | extended.thrift | +| name | extended.thrift | +| napping | extended.thrift | +| nice | extended.thrift | +| num1 | test.thrift | +| num2 | test.thrift | +| op | test.thrift | +| ouch | test.thrift | +| owner | extended.thrift | +| ping | test.thrift | +| pining | extended.thrift | +| resting | extended.thrift | +| shrubbery | extended.thrift | +| shubbery | extended.thrift | +| string | extended.thrift | +| string | test.thrift | +| void | test.thrift | +| w | test.thrift | +| what | extended.thrift | +| what | test.thrift | +| why | extended.thrift | +| why | test.thrift | +| with_annotations | extended.thrift | +| with_throws | extended.thrift | +| wood | test.thrift | +| zip | test.thrift | diff --git a/python/ql/test/library-tests/thrift/File.ql b/python/ql/test/library-tests/thrift/File.ql new file mode 100644 index 000000000000..e4f497dbd01c --- /dev/null +++ b/python/ql/test/library-tests/thrift/File.ql @@ -0,0 +1,7 @@ + +import external.Thrift + + +from ThriftNamedElement t + +select t.getName(), t.getFile().getBaseName() \ No newline at end of file diff --git a/python/ql/test/library-tests/thrift/Function.expected b/python/ql/test/library-tests/thrift/Function.expected new file mode 100644 index 000000000000..7c05f8fb6714 --- /dev/null +++ b/python/ql/test/library-tests/thrift/Function.expected @@ -0,0 +1,20 @@ +| extended.thrift:14:4:14:15 | function getUser | 0 | extended.thrift:14:17:14:24 | field id | +| extended.thrift:14:4:14:15 | function getUser | returns | extended.thrift:14:4:14:7 | type User | +| extended.thrift:14:4:14:15 | function getUser | throws | extended.thrift:14:35:14:47 | field error | +| extended.thrift:39:4:39:12 | function foo | 0 | extended.thrift:39:14:39:21 | field id | +| extended.thrift:39:4:39:12 | function foo | returns | extended.thrift:39:4:39:8 | type int32 | +| extended.thrift:39:4:39:12 | function foo | throws | extended.thrift:40:9:40:21 | field error | +| extended.thrift:39:4:39:12 | function foo | throws | extended.thrift:41:9:41:22 | field cause | +| extended.thrift:50:5:50:18 | function getPet | 0 | extended.thrift:51:9:51:30 | field owner | +| extended.thrift:50:5:50:18 | function getPet | returns | extended.thrift:50:5:50:11 | type Animals | +| extended.thrift:50:5:50:18 | function getPet | throws | extended.thrift:53:9:53:24 | field napping | +| extended.thrift:50:5:50:18 | function getPet | throws | extended.thrift:54:9:54:30 | field pining | +| extended.thrift:50:5:50:18 | function getPet | throws | extended.thrift:55:9:55:35 | field resting | +| extended.thrift:50:5:50:18 | function getPet | throws | extended.thrift:56:9:56:21 | field deaf | +| test.thrift:68:4:68:10 | function add | 0 | test.thrift:68:12:68:21 | field num1 | +| test.thrift:68:4:68:10 | function add | 1 | test.thrift:68:24:68:33 | field num2 | +| test.thrift:68:4:68:10 | function add | returns | test.thrift:68:4:68:6 | type i32 | +| test.thrift:70:4:70:16 | function calculate | 0 | test.thrift:70:18:70:28 | field logid | +| test.thrift:70:4:70:16 | function calculate | 1 | test.thrift:70:31:70:38 | field w | +| test.thrift:70:4:70:16 | function calculate | returns | test.thrift:70:4:70:6 | type i32 | +| test.thrift:70:4:70:16 | function calculate | throws | test.thrift:70:49:70:71 | field ouch | diff --git a/python/ql/test/library-tests/thrift/Function.ql b/python/ql/test/library-tests/thrift/Function.ql new file mode 100644 index 000000000000..ff891bd5ece6 --- /dev/null +++ b/python/ql/test/library-tests/thrift/Function.ql @@ -0,0 +1,12 @@ + +import external.Thrift + +from ThriftFunction t, string n, ThriftElement x +where +exists(int i | x = t.getArgument(i) and n = i.toString()) +or +x = t.getAThrows() and n = "throws" +or +x = t.getReturnType() and n = "returns" + +select t, n, x \ No newline at end of file diff --git a/python/ql/test/library-tests/thrift/References.expected b/python/ql/test/library-tests/thrift/References.expected new file mode 100644 index 000000000000..add320353f8d --- /dev/null +++ b/python/ql/test/library-tests/thrift/References.expected @@ -0,0 +1,4 @@ +| extended.thrift:14:4:14:7 | type User | extended.thrift:3:8:3:11 | struct User | +| extended.thrift:14:4:14:7 | type User | extended.thrift:3:8:3:11 | struct User | +| extended.thrift:51:21:51:24 | type User | extended.thrift:3:8:3:11 | struct User | +| test.thrift:70:33:70:36 | type Work | test.thrift:38:8:38:11 | struct Work | diff --git a/python/ql/test/library-tests/thrift/References.ql b/python/ql/test/library-tests/thrift/References.ql new file mode 100644 index 000000000000..c6621b44e95d --- /dev/null +++ b/python/ql/test/library-tests/thrift/References.ql @@ -0,0 +1,7 @@ + +import python +import external.Thrift + +from ThriftType t, ThriftStruct s +where t.references(s) +select t, s diff --git a/python/ql/test/library-tests/thrift/Service.expected b/python/ql/test/library-tests/thrift/Service.expected new file mode 100644 index 000000000000..60d57f5d5813 --- /dev/null +++ b/python/ql/test/library-tests/thrift/Service.expected @@ -0,0 +1,7 @@ +| extended.thrift:12:9:12:16 | service Extended | getUser | extended.thrift:14:4:14:15 | function getUser | +| extended.thrift:37:9:37:19 | service with_throws | foo | extended.thrift:39:4:39:12 | function foo | +| extended.thrift:48:9:48:15 | service TheShop | getPet | extended.thrift:50:5:50:18 | function getPet | +| test.thrift:57:9:57:18 | service Calculator | add | test.thrift:68:4:68:10 | function add | +| test.thrift:57:9:57:18 | service Calculator | calculate | test.thrift:70:4:70:16 | function calculate | +| test.thrift:57:9:57:18 | service Calculator | ping | test.thrift:66:4:66:12 | function ping | +| test.thrift:57:9:57:18 | service Calculator | zip | test.thrift:77:11:77:18 | function zip | diff --git a/python/ql/test/library-tests/thrift/Service.ql b/python/ql/test/library-tests/thrift/Service.ql new file mode 100644 index 000000000000..801379c6a2ea --- /dev/null +++ b/python/ql/test/library-tests/thrift/Service.ql @@ -0,0 +1,6 @@ + +import external.Thrift + + +from ThriftService service, string name +select service, name, service.getFunction(name) diff --git a/python/ql/test/library-tests/thrift/Test.expected b/python/ql/test/library-tests/thrift/Test.expected new file mode 100644 index 000000000000..8b1717cf09bb --- /dev/null +++ b/python/ql/test/library-tests/thrift/Test.expected @@ -0,0 +1 @@ +| Thrift | diff --git a/python/ql/test/library-tests/thrift/Test.ql b/python/ql/test/library-tests/thrift/Test.ql new file mode 100644 index 000000000000..735b9ad0eaeb --- /dev/null +++ b/python/ql/test/library-tests/thrift/Test.ql @@ -0,0 +1,7 @@ + +import external.Thrift + +from string cls +where any(ThriftElement t).getAQlClass() = cls +select cls.prefix(6) + diff --git a/python/ql/test/library-tests/thrift/Value.expected b/python/ql/test/library-tests/thrift/Value.expected new file mode 100644 index 000000000000..4e0a78ffdc9e --- /dev/null +++ b/python/ql/test/library-tests/thrift/Value.expected @@ -0,0 +1,158 @@ +| extended.thrift:3:8:3:11 | IDENTIFIER User | User | +| extended.thrift:4:3:4:3 | INTCONSTANT 1 | 1 | +| extended.thrift:4:6:4:11 | STRING string | string | +| extended.thrift:4:13:4:16 | IDENTIFIER name | name | +| extended.thrift:7:11:7:15 | IDENTIFIER Error | Error | +| extended.thrift:8:3:8:3 | INTCONSTANT 1 | 1 | +| extended.thrift:8:6:8:8 | I32 i32 | i32 | +| extended.thrift:8:10:8:13 | IDENTIFIER what | what | +| extended.thrift:9:3:9:3 | INTCONSTANT 2 | 2 | +| extended.thrift:9:6:9:11 | STRING string | string | +| extended.thrift:9:13:9:15 | IDENTIFIER why | why | +| extended.thrift:12:9:12:16 | IDENTIFIER Extended | Extended | +| extended.thrift:14:4:14:7 | IDENTIFIER User | User | +| extended.thrift:14:9:14:15 | IDENTIFIER getUser | getUser | +| extended.thrift:14:17:14:17 | INTCONSTANT 1 | 1 | +| extended.thrift:14:19:14:21 | I32 i32 | i32 | +| extended.thrift:14:23:14:24 | IDENTIFIER id | id | +| extended.thrift:14:35:14:35 | INTCONSTANT 1 | 1 | +| extended.thrift:14:37:14:41 | IDENTIFIER Error | Error | +| extended.thrift:14:43:14:47 | IDENTIFIER error | error | +| extended.thrift:15:5:15:16 | IDENTIFIER doggy.window | doggy.window | +| extended.thrift:15:20:15:25 | LITERAL "true" | "true" | +| extended.thrift:15:28:15:40 | IDENTIFIER doggy.howmuch | doggy.howmuch | +| extended.thrift:15:44:15:47 | INTCONSTANT 1000 | 1000 | +| extended.thrift:19:9:19:11 | I32 i32 | i32 | +| extended.thrift:19:13:19:15 | IDENTIFIER int | int | +| extended.thrift:21:9:21:11 | I64 i64 | i64 | +| extended.thrift:21:13:21:19 | IDENTIFIER foo.bar | foo.bar | +| extended.thrift:21:23:21:29 | LITERAL "hello" | "hello" | +| extended.thrift:21:32:21:40 | IDENTIFIER shrubbery | shrubbery | +| extended.thrift:23:8:23:23 | IDENTIFIER with_annotations | with_annotations | +| extended.thrift:25:5:25:5 | INTCONSTANT 1 | 1 | +| extended.thrift:25:17:25:19 | IDENTIFIER int | int | +| extended.thrift:25:21:25:22 | IDENTIFIER i1 | i1 | +| extended.thrift:26:5:26:5 | INTCONSTANT 2 | 2 | +| extended.thrift:26:8:26:10 | IDENTIFIER int | int | +| extended.thrift:26:13:26:25 | IDENTIFIER type.annotate | type.annotate | +| extended.thrift:26:29:26:33 | LITERAL "foo" | "foo" | +| extended.thrift:26:36:26:37 | IDENTIFIER i2 | i2 | +| extended.thrift:27:5:27:5 | INTCONSTANT 3 | 3 | +| extended.thrift:27:8:27:15 | IDENTIFIER shubbery | shubbery | +| extended.thrift:27:17:27:20 | IDENTIFIER nice | nice | +| extended.thrift:27:23:27:37 | IDENTIFIER knights.who.say | knights.who.say | +| extended.thrift:27:41:27:44 | LITERAL "ni" | "ni" | +| extended.thrift:29:5:29:15 | IDENTIFIER struct.anno | struct.anno | +| extended.thrift:29:19:29:21 | LITERAL "y" | "y" | +| extended.thrift:31:6:31:12 | IDENTIFIER Animals | Animals | +| extended.thrift:32:5:32:7 | IDENTIFIER cat | cat | +| extended.thrift:32:11:32:11 | INTCONSTANT 1 | 1 | +| extended.thrift:33:5:33:9 | IDENTIFIER mouse | mouse | +| extended.thrift:33:13:33:13 | INTCONSTANT 2 | 2 | +| extended.thrift:34:5:34:7 | IDENTIFIER dog | dog | +| extended.thrift:35:5:35:13 | IDENTIFIER enum.anno | enum.anno | +| extended.thrift:35:17:35:19 | LITERAL "x" | "x" | +| extended.thrift:37:9:37:19 | IDENTIFIER with_throws | with_throws | +| extended.thrift:39:4:39:8 | IDENTIFIER int32 | int32 | +| extended.thrift:39:10:39:12 | IDENTIFIER foo | foo | +| extended.thrift:39:14:39:14 | INTCONSTANT 1 | 1 | +| extended.thrift:39:16:39:18 | I32 i32 | i32 | +| extended.thrift:39:20:39:21 | IDENTIFIER id | id | +| extended.thrift:40:9:40:9 | INTCONSTANT 1 | 1 | +| extended.thrift:40:11:40:15 | IDENTIFIER Error | Error | +| extended.thrift:40:17:40:21 | IDENTIFIER error | error | +| extended.thrift:41:9:41:9 | INTCONSTANT 3 | 3 | +| extended.thrift:41:11:41:16 | STRING string | string | +| extended.thrift:41:18:41:22 | IDENTIFIER cause | cause | +| extended.thrift:46:14:46:22 | IDENTIFIER shrubbery | shrubbery | +| extended.thrift:46:25:46:30 | IDENTIFIER border | border | +| extended.thrift:46:34:46:35 | IDENTIFIER ni | ni | +| extended.thrift:46:39:46:45 | LITERAL "false" | "false" | +| extended.thrift:48:9:48:15 | IDENTIFIER TheShop | TheShop | +| extended.thrift:50:5:50:11 | IDENTIFIER Animals | Animals | +| extended.thrift:50:13:50:18 | IDENTIFIER getPet | getPet | +| extended.thrift:51:9:51:9 | INTCONSTANT 1 | 1 | +| extended.thrift:51:21:51:24 | IDENTIFIER User | User | +| extended.thrift:51:26:51:30 | IDENTIFIER owner | owner | +| extended.thrift:53:9:53:9 | INTCONSTANT 1 | 1 | +| extended.thrift:53:12:53:16 | IDENTIFIER Error | Error | +| extended.thrift:53:18:53:24 | IDENTIFIER napping | napping | +| extended.thrift:54:9:54:9 | INTCONSTANT 2 | 2 | +| extended.thrift:54:12:54:23 | IDENTIFIER AnotherError | AnotherError | +| extended.thrift:54:25:54:30 | IDENTIFIER pining | pining | +| extended.thrift:55:9:55:9 | INTCONSTANT 3 | 3 | +| extended.thrift:55:12:55:27 | IDENTIFIER ThirdKindOfError | ThirdKindOfError | +| extended.thrift:55:29:55:35 | IDENTIFIER resting | resting | +| extended.thrift:56:9:56:9 | INTCONSTANT 4 | 4 | +| extended.thrift:56:12:56:16 | IDENTIFIER Error | Error | +| extended.thrift:56:18:56:21 | IDENTIFIER deaf | deaf | +| extended.thrift:59:5:59:22 | IDENTIFIER service.annotation | service.annotation | +| extended.thrift:59:26:59:32 | LITERAL "thing" | "thing" | +| test.thrift:3:9:3:23 | LITERAL "shared.thrift" | "shared.thrift" | +| test.thrift:15:7:15:9 | I32 i32 | i32 | +| test.thrift:15:11:15:23 | IDENTIFIER INT32CONSTANT | INT32CONSTANT | +| test.thrift:15:27:15:30 | INTCONSTANT 9853 | 9853 | +| test.thrift:16:11:16:16 | STRING string | string | +| test.thrift:16:18:16:23 | STRING string | string | +| test.thrift:16:26:16:36 | IDENTIFIER MAPCONSTANT | MAPCONSTANT | +| test.thrift:16:41:16:47 | LITERAL 'hello' | 'hello' | +| test.thrift:16:49:16:55 | LITERAL 'world' | 'world' | +| test.thrift:16:58:16:68 | LITERAL 'goodnight' | 'goodnight' | +| test.thrift:16:70:16:75 | LITERAL 'moon' | 'moon' | +| test.thrift:22:6:22:14 | IDENTIFIER Operation | Operation | +| test.thrift:23:3:23:5 | IDENTIFIER ADD | ADD | +| test.thrift:23:9:23:9 | INTCONSTANT 1 | 1 | +| test.thrift:24:3:24:10 | IDENTIFIER SUBTRACT | SUBTRACT | +| test.thrift:24:14:24:14 | INTCONSTANT 2 | 2 | +| test.thrift:25:3:25:10 | IDENTIFIER MULTIPLY | MULTIPLY | +| test.thrift:25:14:25:14 | INTCONSTANT 3 | 3 | +| test.thrift:26:3:26:8 | IDENTIFIER DIVIDE | DIVIDE | +| test.thrift:26:12:26:12 | INTCONSTANT 4 | 4 | +| test.thrift:38:8:38:11 | IDENTIFIER Work | Work | +| test.thrift:39:3:39:3 | INTCONSTANT 1 | 1 | +| test.thrift:39:6:39:8 | I32 i32 | i32 | +| test.thrift:39:10:39:13 | IDENTIFIER num1 | num1 | +| test.thrift:39:17:39:17 | INTCONSTANT 0 | 0 | +| test.thrift:40:3:40:3 | INTCONSTANT 2 | 2 | +| test.thrift:40:6:40:8 | I32 i32 | i32 | +| test.thrift:40:10:40:13 | IDENTIFIER num2 | num2 | +| test.thrift:41:3:41:3 | INTCONSTANT 3 | 3 | +| test.thrift:41:6:41:14 | IDENTIFIER Operation | Operation | +| test.thrift:41:16:41:17 | IDENTIFIER op | op | +| test.thrift:42:3:42:3 | INTCONSTANT 4 | 4 | +| test.thrift:42:15:42:20 | STRING string | string | +| test.thrift:42:22:42:28 | IDENTIFIER comment | comment | +| test.thrift:48:11:48:26 | IDENTIFIER InvalidOperation | InvalidOperation | +| test.thrift:49:3:49:3 | INTCONSTANT 1 | 1 | +| test.thrift:49:6:49:8 | I32 i32 | i32 | +| test.thrift:49:10:49:13 | IDENTIFIER what | what | +| test.thrift:50:3:50:3 | INTCONSTANT 2 | 2 | +| test.thrift:50:6:50:11 | STRING string | string | +| test.thrift:50:13:50:15 | IDENTIFIER why | why | +| test.thrift:57:9:57:18 | IDENTIFIER Calculator | Calculator | +| test.thrift:57:28:57:47 | IDENTIFIER shared.SharedService | shared.SharedService | +| test.thrift:66:4:66:7 | VOID void | void | +| test.thrift:66:9:66:12 | IDENTIFIER ping | ping | +| test.thrift:68:4:68:6 | I32 i32 | i32 | +| test.thrift:68:8:68:10 | IDENTIFIER add | add | +| test.thrift:68:12:68:12 | INTCONSTANT 1 | 1 | +| test.thrift:68:14:68:16 | I32 i32 | i32 | +| test.thrift:68:18:68:21 | IDENTIFIER num1 | num1 | +| test.thrift:68:24:68:24 | INTCONSTANT 2 | 2 | +| test.thrift:68:26:68:28 | I32 i32 | i32 | +| test.thrift:68:30:68:33 | IDENTIFIER num2 | num2 | +| test.thrift:70:4:70:6 | I32 i32 | i32 | +| test.thrift:70:8:70:16 | IDENTIFIER calculate | calculate | +| test.thrift:70:18:70:18 | INTCONSTANT 1 | 1 | +| test.thrift:70:20:70:22 | I32 i32 | i32 | +| test.thrift:70:24:70:28 | IDENTIFIER logid | logid | +| test.thrift:70:31:70:31 | INTCONSTANT 2 | 2 | +| test.thrift:70:33:70:36 | IDENTIFIER Work | Work | +| test.thrift:70:38:70:38 | IDENTIFIER w | w | +| test.thrift:70:49:70:49 | INTCONSTANT 1 | 1 | +| test.thrift:70:51:70:66 | IDENTIFIER InvalidOperation | InvalidOperation | +| test.thrift:70:68:70:71 | IDENTIFIER ouch | ouch | +| test.thrift:77:11:77:14 | VOID void | void | +| test.thrift:77:16:77:18 | IDENTIFIER zip | zip | +| test.thrift:83:9:83:12 | IDENTIFIER wood | wood | +| test.thrift:83:14:83:17 | IDENTIFIER duck | duck | diff --git a/python/ql/test/library-tests/thrift/Value.ql b/python/ql/test/library-tests/thrift/Value.ql new file mode 100644 index 000000000000..7cf83b1df65d --- /dev/null +++ b/python/ql/test/library-tests/thrift/Value.ql @@ -0,0 +1,5 @@ + +import external.Thrift + +from ThriftElement t +select t, t.getValue() \ No newline at end of file diff --git a/python/ql/test/library-tests/thrift/extended.thrift b/python/ql/test/library-tests/thrift/extended.thrift new file mode 100644 index 000000000000..7b17155c6a43 --- /dev/null +++ b/python/ql/test/library-tests/thrift/extended.thrift @@ -0,0 +1,60 @@ + + +struct User { + 1: string name +} + +exception Error { + 1: i32 what, + 2: string why +} + +service Extended { + + User getUser(1:i32 id) throws (1:Error error) + (doggy.window = "true", doggy.howmuch = 1000) + +} + +typedef i32 int; + +typedef i64(foo.bar = "hello") shrubbery; + +struct with_annotations { + + 1: optional int i1; + 2: int (type.annotate = "foo") i2; + 3: shubbery nice (knights.who.say = "ni"); + +} ( struct.anno = "y" ) + +enum Animals { + cat = 1, + mouse = 2, + dog +} ( enum.anno = "x" ) + +service with_throws { + + int32 foo(1:i32 id) throws ( + 1:Error error + 3:string cause + ) + +} + +typedef list border ( ni = "false" ) + +service TheShop { + + Animals getPet( + 1: required User owner + ) throws ( + 1: Error napping + 2: AnotherError pining + 3: ThirdKindOfError resting + 4: Error deaf + ) +} ( + service.annotation = "thing" +) diff --git a/python/ql/test/library-tests/thrift/options b/python/ql/test/library-tests/thrift/options new file mode 100644 index 000000000000..3eeb07bd643e --- /dev/null +++ b/python/ql/test/library-tests/thrift/options @@ -0,0 +1,2 @@ +semmle-extractor-options: -R . --filter=include:**/*.thrift --filter exclude:**/src_archive/** +automatic_locations: true diff --git a/python/ql/test/library-tests/thrift/test.py b/python/ql/test/library-tests/thrift/test.py new file mode 100644 index 000000000000..73d0ccb36306 --- /dev/null +++ b/python/ql/test/library-tests/thrift/test.py @@ -0,0 +1 @@ +#Check that thrift extractor works when there is a Python file with the same stem \ No newline at end of file diff --git a/python/ql/test/library-tests/thrift/test.thrift b/python/ql/test/library-tests/thrift/test.thrift new file mode 100644 index 000000000000..73a78b6c9796 --- /dev/null +++ b/python/ql/test/library-tests/thrift/test.thrift @@ -0,0 +1,84 @@ + + +include "shared.thrift" + +namespace cpp tutorial +namespace d d +namespace java a.b.c.d.com +namespace php tutorial +namespace go xxxx + +/** + * Thrift also lets you define constants for use across languages. Complex + * types and structs are specified using JSON notation. + */ +const i32 INT32CONSTANT = 9853 +const map MAPCONSTANT = {'hello':'world', 'goodnight':'moon'} + +/** + * You can define enums, which are just 32 bit integers. Values are optional + * and start at 1 if not supplied, C style again. + */ +enum Operation { + ADD = 1, + SUBTRACT = 2, + MULTIPLY = 3, + DIVIDE = 4 +} + +/** + * Structs are the basic complex data structures. They are comprised of fields + * which each have an integer identifier, a type, a symbolic name, and an + * optional default value. + * + * Fields can be declared "optional", which ensures they will not be included + * in the serialized output if they aren't set. Note that this requires some + * manual management in some languages. + */ +struct Work { + 1: i32 num1 = 0, + 2: i32 num2, + 3: Operation op, + 4: optional string comment, +} + +/** + * Structs can also be exceptions, if they are nasty. + */ +exception InvalidOperation { + 1: i32 what, + 2: string why +} + +/** + * Ahh, now onto the cool part, defining a service. Services just need a name + * and can optionally inherit from another service using the extends keyword. + */ +service Calculator extends shared.SharedService { + + /** + * A method definition looks like C code. It has a return type, arguments, + * and optionally a list of exceptions that it may throw. Note that argument + * lists and exception lists are specified using the exact same syntax as + * field lists in struct or exception definitions. + */ + + void ping(), + + i32 add(1:i32 num1, 2:i32 num2), + + i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), + + /** + * This method has a oneway modifier. That means the client only makes + * a request and does not listen for any response at all. Oneway methods + * must be void. + */ + oneway void zip() + +} + +#Python-style comments are allowed + +typedef wood duck + diff --git a/python/ql/test/library-tests/types/attributes/Test.expected b/python/ql/test/library-tests/types/attributes/Test.expected new file mode 100644 index 000000000000..d0d7283d4cdc --- /dev/null +++ b/python/ql/test/library-tests/types/attributes/Test.expected @@ -0,0 +1,10 @@ +| 3 | class Base | class Base | C | class C | 5 | +| 3 | class Base | class Base | meth | Function meth | 7 | +| 10 | class Derived | class Base | C | class C | 5 | +| 10 | class Derived | class Base | meth | Function meth | 7 | +| 10 | class Derived | class Derived | C | class C | 5 | +| 10 | class Derived | class Derived | meth | Function meth | 12 | +| 16 | class Derived2 | class Base | C | class C | 5 | +| 16 | class Derived2 | class Base | meth | Function meth | 7 | +| 16 | class Derived2 | class Derived2 | C | class C | 21 | +| 16 | class Derived2 | class Derived2 | meth | Function meth | 18 | diff --git a/python/ql/test/library-tests/types/attributes/Test.ql b/python/ql/test/library-tests/types/attributes/Test.ql new file mode 100644 index 000000000000..a92064a25516 --- /dev/null +++ b/python/ql/test/library-tests/types/attributes/Test.ql @@ -0,0 +1,6 @@ +import python + +from ClassObject cls, ClassObject start, string name, Object val +where not name.substring(0, 2) = "__" and val = cls.lookupMro(start, name) +select +cls.getOrigin().getLocation().getStartLine(), cls.toString(), start.toString(), name, val.toString(), val.getOrigin().getLocation().getStartLine() \ No newline at end of file diff --git a/python/ql/test/library-tests/types/attributes/test.py b/python/ql/test/library-tests/types/attributes/test.py new file mode 100644 index 000000000000..1f3ba3def1bc --- /dev/null +++ b/python/ql/test/library-tests/types/attributes/test.py @@ -0,0 +1,21 @@ + + +class Base(object): + + class C(object): pass + + def meth(self): + pass + +class Derived(Base): + + def meth(self): + super(Derived, self).meth() + super(Derived, self).x + +class Derived2(Base): + + def meth(self): + pass + + class C(object): pass diff --git a/python/ql/test/library-tests/types/classattr/ClassAttribute.expected b/python/ql/test/library-tests/types/classattr/ClassAttribute.expected new file mode 100644 index 000000000000..6dad8a5b12cf --- /dev/null +++ b/python/ql/test/library-tests/types/classattr/ClassAttribute.expected @@ -0,0 +1,45 @@ +| class Base | declares | x | +| class Base | has | x | +| class Base2 | declares | x | +| class Base2 | has | x | +| class D1 | has | x | +| class D2 | has | x | +| class D3 | has | x | +| class Diamond | has | x | +| class New | declares | a1 | +| class New | declares | a2 | +| class New | declares | i1 | +| class New | declares | i2 | +| class New | declares | u1 | +| class New | declares | u2 | +| class New | has | a1 | +| class New | has | a2 | +| class New | has | i1 | +| class New | has | i2 | +| class New | has | u1 | +| class New | has | u2 | +| class NewDerived | has | a1 | +| class NewDerived | has | a2 | +| class NewDerived | has | i1 | +| class NewDerived | has | i2 | +| class NewDerived | has | u1 | +| class NewDerived | has | u2 | +| class Old | declares | a1 | +| class Old | declares | a2 | +| class Old | declares | i1 | +| class Old | declares | i2 | +| class Old | declares | u1 | +| class Old | declares | u2 | +| class Old | has | a1 | +| class Old | has | a2 | +| class Old | has | i1 | +| class Old | has | i2 | +| class Old | has | u1 | +| class Old | has | u2 | +| class OldDerived | has | a1 | +| class OldDerived | has | a2 | +| class OldDerived | has | i1 | +| class OldDerived | has | i2 | +| class OldDerived | has | u1 | +| class OldDerived | has | u2 | +| class Tree | has | x | diff --git a/python/ql/test/library-tests/types/classattr/ClassAttribute.ql b/python/ql/test/library-tests/types/classattr/ClassAttribute.ql new file mode 100644 index 000000000000..6895020718e7 --- /dev/null +++ b/python/ql/test/library-tests/types/classattr/ClassAttribute.ql @@ -0,0 +1,19 @@ +/** + * @name ClassAttribute + * @description Test for Class attributes + * @kind test + */ + +import python + +from ClassObject cls, string name, string kind +where +not cls.isC() and +not name.matches("\\_\\_%\\_\\_") and +( + cls.hasAttribute(name) and kind = "has" + or + cls.declaresAttribute(name) and kind = "declares" +) +select cls.toString(), kind ,name + diff --git a/python/ql/test/library-tests/types/classattr/ClassMember.expected b/python/ql/test/library-tests/types/classattr/ClassMember.expected new file mode 100644 index 000000000000..24d4faa28718 --- /dev/null +++ b/python/ql/test/library-tests/types/classattr/ClassMember.expected @@ -0,0 +1,27 @@ +| class Base | declares | x | int 1 | +| class Base | has | x | int 1 | +| class Base2 | declares | x | int 2 | +| class Base2 | has | x | int 2 | +| class D1 | has | x | int 1 | +| class D2 | has | x | int 1 | +| class D3 | has | x | int 2 | +| class Diamond | has | x | int 1 | +| class New | declares | a2 | int 7 | +| class New | declares | i1 | int 5 | +| class New | declares | i2 | int 5 | +| class New | has | a2 | int 7 | +| class New | has | i1 | int 5 | +| class New | has | i2 | int 5 | +| class NewDerived | has | a2 | int 7 | +| class NewDerived | has | i1 | int 5 | +| class NewDerived | has | i2 | int 5 | +| class Old | declares | a2 | int 7 | +| class Old | declares | i1 | int 5 | +| class Old | declares | i2 | int 5 | +| class Old | has | a2 | int 7 | +| class Old | has | i1 | int 5 | +| class Old | has | i2 | int 5 | +| class OldDerived | has | a2 | int 7 | +| class OldDerived | has | i1 | int 5 | +| class OldDerived | has | i2 | int 5 | +| class Tree | has | x | int 1 | diff --git a/python/ql/test/library-tests/types/classattr/ClassMember.ql b/python/ql/test/library-tests/types/classattr/ClassMember.ql new file mode 100644 index 000000000000..b76851dd43de --- /dev/null +++ b/python/ql/test/library-tests/types/classattr/ClassMember.ql @@ -0,0 +1,18 @@ +/** + * @name ClassAttribute + * @description Test for Class attributes + * @kind test + */ + +import python + +from ClassObject cls, string name, string kind, Object o +where +not cls.isC() and +not name.matches("\\_\\_%\\_\\_") and +( + o = cls.lookupAttribute(name) and kind = "has" + or + o = cls.declaredAttribute(name) and kind = "declares" +) +select cls.toString(), kind, name, o.toString() diff --git a/python/ql/test/library-tests/types/classattr/SpecialAttribute.expected b/python/ql/test/library-tests/types/classattr/SpecialAttribute.expected new file mode 100644 index 000000000000..c27c4e9c83f4 --- /dev/null +++ b/python/ql/test/library-tests/types/classattr/SpecialAttribute.expected @@ -0,0 +1,6 @@ +| class Special1 | declares | __add__ | int 1 | +| class Special1 | has | __add__ | int 1 | +| class Special2 | declares | __float__ | int 2 | +| class Special2 | has | __float__ | int 2 | +| class Special3 | has | __add__ | int 1 | +| class Special3 | has | __float__ | int 2 | diff --git a/python/ql/test/library-tests/types/classattr/SpecialAttribute.ql b/python/ql/test/library-tests/types/classattr/SpecialAttribute.ql new file mode 100644 index 000000000000..cdfd29b8d917 --- /dev/null +++ b/python/ql/test/library-tests/types/classattr/SpecialAttribute.ql @@ -0,0 +1,14 @@ + +import python + +from ClassObject cls, string name, string kind, Object o +where +not cls.isC() and +name.matches("\\_\\_%\\_\\_") and +not o = theObjectType().lookupAttribute(name) and +( + o = cls.lookupAttribute(name) and kind = "has" + or + o = cls.declaredAttribute(name) and kind = "declares" +) +select cls.toString(), kind, name, o.toString() diff --git a/python/ql/test/library-tests/types/classattr/classattr.py b/python/ql/test/library-tests/types/classattr/classattr.py new file mode 100644 index 000000000000..23dc24499f7a --- /dev/null +++ b/python/ql/test/library-tests/types/classattr/classattr.py @@ -0,0 +1,64 @@ +#Test various permutations of known and unknown values, old and new style class and shadowed and unshadowed names. + +from unknown import u1 +i1 = 5 +from unknown import g1 +g2 = 7 + + +class Old: + + a1 = g1 + a2 = g2 + u2 = u1 + u1 = u1 + i2 = i1 + i1 = i1 + +class New(object): + + a1 = g1 + a2 = g2 + u2 = u1 + u1 = u1 + i2 = i1 + i1 = i1 + +class OldDerived(Old): + pass + +class NewDerived(New): + pass + +class Base(object): + x = 1 + +class D1(Base): + pass + +class D2(Base): + pass + +class Diamond(D1, D2): + pass + +class Base2(object): + x = 2 + +class D3(Base2): + pass + + +class Tree(D1, D3): + pass + +class Special1(object): + __add__ = 1 + +class Special2(object): + __float__ = 2 + +class Special3(Special1, Special2): + pass + + diff --git a/python/ql/test/library-tests/types/classes/FailedInference.expected b/python/ql/test/library-tests/types/classes/FailedInference.expected new file mode 100644 index 000000000000..916075a656c2 --- /dev/null +++ b/python/ql/test/library-tests/types/classes/FailedInference.expected @@ -0,0 +1,25 @@ +| circular_inheritance.py:33:1:33:11 | class A | Missing base 0 | +| circular_inheritance.py:37:1:37:11 | class B | Missing base 0 | +| circular_inheritance.py:40:1:40:72 | class D | Duplicate bases classes | +| circular_inheritance.py:43:1:43:41 | class E | Duplicate bases classes | +| circular_inheritance.py:43:1:43:41 | class E | Failed inference for base class at position 0 | +| circular_inheritance.py:43:1:43:41 | class E | Failed inference for base class at position 1 | +| circular_inheritance.py:43:1:43:41 | class E | Failed inference for base class at position 2 | +| circular_inheritance.py:43:1:43:41 | class E | Failed inference for base class at position 3 | +| circular_inheritance.py:43:1:43:41 | class E | Failed inference for base class at position 4 | +| circular_inheritance.py:43:1:43:41 | class E | Failed inference for base class at position 5 | +| circular_inheritance.py:43:1:43:41 | class E | Failed inference for base class at position 6 | +| circular_inheritance.py:43:1:43:41 | class E | Failed inference for base class at position 7 | +| circular_inheritance.py:43:1:43:41 | class E | Failed inference for base class at position 8 | +| circular_inheritance.py:43:1:43:41 | class E | Failed inference for base class at position 9 | +| circular_inheritance.py:43:1:43:41 | class E | Failed inference for base class at position 10 | +| circular_inheritance.py:47:1:47:19 | class G | Missing base 0 | +| circular_inheritance.py:50:1:50:19 | class J | Missing base 0 | +| circular_inheritance.py:54:1:54:11 | class M | Missing base 0 | +| circular_inheritance.py:57:1:57:11 | class L | Missing base 0 | +| circular_inheritance.py:61:1:61:19 | class S | Missing base 0 | +| circular_inheritance.py:64:1:64:19 | class R | Missing base 0 | +| mutual_inheritance.py:4:1:4:11 | class C | Missing base 0 | +| mutual_inheritance.py:8:1:8:19 | class H | Missing base 0 | +| mutual_inheritance.py:12:1:12:11 | class N | Missing base 0 | +| mutual_inheritance.py:15:1:15:19 | class T | Missing base 0 | diff --git a/python/ql/test/library-tests/types/classes/FailedInference.ql b/python/ql/test/library-tests/types/classes/FailedInference.ql new file mode 100644 index 000000000000..129c17ffd9df --- /dev/null +++ b/python/ql/test/library-tests/types/classes/FailedInference.ql @@ -0,0 +1,11 @@ + +import python +import semmle.python.pointsto.PointsTo + +from ClassObject cls, string reason + +where +PointsTo::Types::failed_inference(cls, reason) + +select cls, reason + diff --git a/python/ql/test/library-tests/types/classes/c_inheritance.py b/python/ql/test/library-tests/types/classes/c_inheritance.py new file mode 100644 index 000000000000..92656455660c --- /dev/null +++ b/python/ql/test/library-tests/types/classes/c_inheritance.py @@ -0,0 +1,6 @@ + +class MyList(list): + pass + +class MyDict(dict): + pass diff --git a/python/ql/test/library-tests/types/classes/circular_inheritance.py b/python/ql/test/library-tests/types/classes/circular_inheritance.py new file mode 100644 index 000000000000..5c23f152f1bb --- /dev/null +++ b/python/ql/test/library-tests/types/classes/circular_inheritance.py @@ -0,0 +1,67 @@ +from mutual_inheritance import C, H, N, T + +#Good newstyle classes + +class W(object): + pass + +class X(W): + pass + +class Y(X): + pass + +class Z(Y, X): + pass + +#Good oldstyle classes + +class O1: + pass + +class O2(O1): + pass + +class O3(O1): + pass + +class O4(O2, O3): + pass + +#Bad classes -- Illegal and designed to break MRO computation + +class A(A): + pass + +#Two way cycle +class B(C): + pass + +class D(object, object, object, object, object, object, object, object): + pass + +class E(D, D, D, D, D, D, D, D, D, D, D): + pass + +#Two way cycle with object +class G(H, object): + pass + +class J(J, object): + pass + +#Three way cycle +class M(N): + pass + +class L(M): + pass + +#Three way cycle with object +class S(T, object): + pass + +class R(S, object): + pass + + diff --git a/python/ql/test/library-tests/types/classes/duplicate_base.expected b/python/ql/test/library-tests/types/classes/duplicate_base.expected new file mode 100644 index 000000000000..7f41c6bbe8a8 --- /dev/null +++ b/python/ql/test/library-tests/types/classes/duplicate_base.expected @@ -0,0 +1,2 @@ +| class D | +| class E | diff --git a/python/ql/test/library-tests/types/classes/duplicate_base.ql b/python/ql/test/library-tests/types/classes/duplicate_base.ql new file mode 100644 index 000000000000..4f865754088e --- /dev/null +++ b/python/ql/test/library-tests/types/classes/duplicate_base.ql @@ -0,0 +1,7 @@ + +import python + +from ClassObject cls +where cls.hasDuplicateBases() +select cls.toString() + diff --git a/python/ql/test/library-tests/types/classes/mutual_inheritance.py b/python/ql/test/library-tests/types/classes/mutual_inheritance.py new file mode 100644 index 000000000000..4852bace9bec --- /dev/null +++ b/python/ql/test/library-tests/types/classes/mutual_inheritance.py @@ -0,0 +1,17 @@ +from circular_inheritance import B, G, L, R + +#Two way cycle +class C(B): + pass + +#Two way cycle with object +class H(G, object): + pass + +#Three way cycle +class N(L): + pass + +class T(R, object): + pass + diff --git a/python/ql/test/library-tests/types/exceptions/ExitRaises.expected b/python/ql/test/library-tests/types/exceptions/ExitRaises.expected new file mode 100644 index 000000000000..1c54a818dbbd --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/ExitRaises.expected @@ -0,0 +1,28 @@ +| 16 | test.py:16:5:16:22 | ControlFlowNode for Raise | Function f2 | test.py:3:1:3:28 | class ExceptionA | +| 22 | test.py:22:5:22:8 | ControlFlowNode for f2() | Function f5 | test.py:3:1:3:28 | class ExceptionA | +| 25 | test.py:25:5:25:22 | ControlFlowNode for Raise | Function f6 | test.py:6:1:6:28 | class ExceptionB | +| 30 | test.py:30:9:30:26 | ControlFlowNode for Raise | Function f7 | test.py:3:1:3:28 | class ExceptionA | +| 32 | test.py:32:9:32:26 | ControlFlowNode for Raise | Function f7 | test.py:6:1:6:28 | class ExceptionB | +| 36 | test.py:36:9:36:12 | ControlFlowNode for f7() | Function f8 | test.py:3:1:3:28 | class ExceptionA | +| 44 | test.py:44:9:44:12 | ControlFlowNode for Pass | Function f8a | test.py:3:1:3:28 | class ExceptionA | +| 44 | test.py:44:9:44:12 | ControlFlowNode for Pass | Function f8a | test.py:6:1:6:28 | class ExceptionB | +| 53 | test.py:53:9:53:12 | ControlFlowNode for Pass | Function f9 | test.py:3:1:3:28 | class ExceptionA | +| 71 | test.py:71:9:71:26 | ControlFlowNode for Raise | Function f11 | test.py:9:1:9:28 | class ExceptionC | +| 71 | test.py:71:9:71:26 | ControlFlowNode for Raise | Function f11 | test.py:9:1:9:28 | class ExceptionC | +| 74 | test.py:74:5:74:14 | ControlFlowNode for Subscript | Function f12 | file://:Compiled Code:0:0:0:0 | builtin-class LookupError | +| 107 | test.py:107:9:107:12 | ControlFlowNode for Pass | Function f18 | file://:Compiled Code:0:0:0:0 | builtin-class LookupError | +| 110 | test.py:110:5:110:22 | ControlFlowNode for Raise | Function f19 | file://:Compiled Code:0:0:0:0 | builtin-class IndexError | +| 120 | test.py:120:9:120:13 | ControlFlowNode for f19() | Function f21 | file://:Compiled Code:0:0:0:0 | builtin-class IndexError | +| 128 | test.py:128:9:128:12 | ControlFlowNode for Pass | Function f22 | file://:Compiled Code:0:0:0:0 | builtin-class IndexError | +| 138 | test.py:138:13:138:16 | ControlFlowNode for Pass | Function f23 | test.py:3:1:3:28 | class ExceptionA | +| 138 | test.py:138:13:138:16 | ControlFlowNode for Pass | Function f23 | test.py:6:1:6:28 | class ExceptionB | +| 138 | test.py:138:13:138:16 | ControlFlowNode for Pass | Function f23 | test.py:9:1:9:28 | class ExceptionC | +| 138 | test.py:138:13:138:16 | ControlFlowNode for Pass | Function f23 | test.py:9:1:9:28 | class ExceptionC | +| 152 | test.py:152:9:152:12 | ControlFlowNode for Pass | Function f24 | test.py:3:1:3:28 | class ExceptionA | +| 152 | test.py:152:9:152:12 | ControlFlowNode for Pass | Function f24 | test.py:6:1:6:28 | class ExceptionB | +| 171 | test.py:171:13:171:16 | ControlFlowNode for Pass | Function f25 | test.py:9:1:9:28 | class ExceptionC | +| 171 | test.py:171:13:171:16 | ControlFlowNode for Pass | Function f25 | test.py:9:1:9:28 | class ExceptionC | +| 172 | test.py:172:9:172:12 | ControlFlowNode for Pass | Function f25 | test.py:3:1:3:28 | class ExceptionA | +| 172 | test.py:172:9:172:12 | ControlFlowNode for Pass | Function f25 | test.py:6:1:6:28 | class ExceptionB | +| 178 | test.py:178:9:178:12 | ControlFlowNode for f7() | Function f26 | test.py:6:1:6:28 | class ExceptionB | +| 183 | test.py:183:9:183:26 | ControlFlowNode for Raise | Function f26 | test.py:9:1:9:28 | class ExceptionC | diff --git a/python/ql/test/library-tests/types/exceptions/ExitRaises.ql b/python/ql/test/library-tests/types/exceptions/ExitRaises.ql new file mode 100644 index 000000000000..efa1f66b8d81 --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/ExitRaises.ql @@ -0,0 +1,6 @@ +import python + +from RaisingNode r, Scope s, ClassObject cls +where r.viableExceptionalExit(s, cls) + +select r.getLocation().getStartLine(), r, s.toString(), cls diff --git a/python/ql/test/library-tests/types/exceptions/Handles.expected b/python/ql/test/library-tests/types/exceptions/Handles.expected new file mode 100644 index 000000000000..4fa3c5505c5f --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/Handles.expected @@ -0,0 +1,16 @@ +| 37 | class ExceptionB | +| 50 | class ExceptionB | +| 59 | class ExceptionB | +| 68 | class ExceptionB | +| 82 | builtin-class KeyError | +| 88 | builtin-class IndexError | +| 94 | builtin-class AttributeError | +| 100 | class ExceptionA | +| 115 | builtin-class IndexError | +| 121 | builtin-class KeyError | +| 181 | class ExceptionA | +| 191 | class A | +| 197 | class ExceptionA | +| 197 | class ExceptionB | +| 201 | builtin-class KeyError | +| 201 | class ExceptionC | diff --git a/python/ql/test/library-tests/types/exceptions/Handles.ql b/python/ql/test/library-tests/types/exceptions/Handles.ql new file mode 100644 index 000000000000..51ceba1a6fb0 --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/Handles.ql @@ -0,0 +1,7 @@ + + +import python + +from ExceptFlowNode n, ClassObject cls +where n.handles(cls) +select n.getLocation().getStartLine(), cls.toString() diff --git a/python/ql/test/library-tests/types/exceptions/Impossible.expected b/python/ql/test/library-tests/types/exceptions/Impossible.expected new file mode 100644 index 000000000000..6df55b7bd7e9 --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/Impossible.expected @@ -0,0 +1,47 @@ +| 22 | 21 | f2() | Function f5 | normal | +| 36 | 34 | f7() | Function f8 | normal | +| 36 | 37 | f7 | ExceptStmt | exceptional | +| 42 | 44 | f7 | Pass | exceptional | +| 42 | 44 | f7() | Pass | normal | +| 49 | 50 | f7 | ExceptStmt | exceptional | +| 49 | 53 | f7 | Pass | exceptional | +| 49 | 53 | f7() | Pass | normal | +| 50 | 53 | ExceptionB | Pass | exceptional | +| 58 | 59 | f7 | ExceptStmt | exceptional | +| 58 | 62 | f7 | Return | exceptional | +| 58 | 62 | f7() | Return | normal | +| 59 | 62 | ExceptionB | Return | exceptional | +| 67 | 68 | f7 | ExceptStmt | exceptional | +| 67 | 71 | f7 | ExceptionC | exceptional | +| 67 | 71 | f7() | ExceptionC | normal | +| 68 | 71 | ExceptionB | ExceptionC | exceptional | +| 81 | 82 | x | ExceptStmt | exceptional | +| 87 | 88 | x | ExceptStmt | exceptional | +| 93 | 94 | x | ExceptStmt | exceptional | +| 99 | 100 | Attribute | ExceptStmt | exceptional | +| 99 | 100 | x | ExceptStmt | exceptional | +| 105 | 107 | x | Pass | exceptional | +| 114 | 112 | f19() | Function f20 | normal | +| 114 | 115 | f19 | ExceptStmt | exceptional | +| 120 | 118 | f19() | Function f21 | normal | +| 120 | 121 | f19 | ExceptStmt | exceptional | +| 120 | 121 | f19() | ExceptStmt | exceptional | +| 126 | 128 | f19 | Pass | exceptional | +| 126 | 128 | f19() | Pass | normal | +| 132 | 134 | f7 | Try | exceptional | +| 132 | 134 | f7() | Try | normal | +| 135 | 138 | x | Pass | exceptional | +| 136 | 138 | ExceptionC | Pass | exceptional | +| 136 | 138 | ExceptionC() | Pass | exceptional | +| 146 | 147 | f7() | Pass | normal | +| 146 | 150 | f7 | Pass | exceptional | +| 158 | 159 | f7() | Pass | normal | +| 158 | 162 | f7 | Try | exceptional | +| 164 | 169 | x | Pass | exceptional | +| 166 | 169 | ExceptionC | Pass | exceptional | +| 166 | 169 | ExceptionC() | Pass | exceptional | +| 178 | 179 | f7() | Pass | normal | +| 178 | 181 | f7 | ExceptStmt | exceptional | +| 189 | 191 | A() | ExceptStmt | exceptional | +| 196 | 197 | func | ExceptStmt | exceptional | +| 200 | 201 | func | ExceptStmt | exceptional | diff --git a/python/ql/test/library-tests/types/exceptions/Impossible.ql b/python/ql/test/library-tests/types/exceptions/Impossible.ql new file mode 100644 index 000000000000..e215a7e96caf --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/Impossible.ql @@ -0,0 +1,20 @@ + + +import python + +from RaisingNode r, ControlFlowNode n, string kind +where r.unlikelySuccessor(n) and +( + r.getATrueSuccessor() = n and kind = "true" + or + r.getAFalseSuccessor() = n and kind = "false" + or + r.getAnExceptionalSuccessor() = n and kind = "exceptional" + or + not r.getATrueSuccessor() = n and + not r.getAFalseSuccessor() = n and + not r.getAnExceptionalSuccessor() = n and + kind = "normal" + +) +select r.getLocation().getStartLine(), n.getLocation().getStartLine(), r.getNode().toString(), n.getNode().toString(), kind diff --git a/python/ql/test/library-tests/types/exceptions/LineRaises.expected b/python/ql/test/library-tests/types/exceptions/LineRaises.expected new file mode 100644 index 000000000000..fc08cde2a53e --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/LineRaises.expected @@ -0,0 +1,93 @@ +| 3 | None | +| 6 | None | +| 9 | None | +| 16 | None | +| 16 | class ExceptionA | +| 22 | class ExceptionA | +| 25 | None | +| 25 | class ExceptionB | +| 30 | None | +| 30 | class ExceptionA | +| 32 | None | +| 32 | class ExceptionB | +| 36 | None | +| 36 | class ExceptionA | +| 36 | class ExceptionB | +| 42 | None | +| 42 | class ExceptionA | +| 42 | class ExceptionB | +| 44 | class ExceptionA | +| 44 | class ExceptionB | +| 49 | None | +| 49 | class ExceptionA | +| 49 | class ExceptionB | +| 50 | None | +| 53 | class ExceptionA | +| 58 | None | +| 58 | class ExceptionA | +| 58 | class ExceptionB | +| 59 | None | +| 67 | None | +| 67 | class ExceptionA | +| 67 | class ExceptionB | +| 68 | None | +| 71 | None | +| 71 | class ExceptionC | +| 74 | builtin-class LookupError | +| 81 | None | +| 81 | builtin-class KeyError | +| 87 | None | +| 87 | builtin-class IndexError | +| 93 | None | +| 93 | builtin-class AttributeError | +| 99 | None | +| 105 | None | +| 105 | builtin-class LookupError | +| 107 | builtin-class LookupError | +| 110 | None | +| 110 | builtin-class IndexError | +| 114 | None | +| 114 | builtin-class IndexError | +| 120 | None | +| 120 | builtin-class IndexError | +| 126 | None | +| 126 | builtin-class IndexError | +| 128 | builtin-class IndexError | +| 132 | None | +| 132 | class ExceptionA | +| 132 | class ExceptionB | +| 135 | None | +| 136 | None | +| 136 | class ExceptionC | +| 138 | class ExceptionA | +| 138 | class ExceptionB | +| 138 | class ExceptionC | +| 146 | None | +| 146 | class ExceptionA | +| 146 | class ExceptionB | +| 152 | class ExceptionA | +| 152 | class ExceptionB | +| 158 | None | +| 158 | class ExceptionA | +| 158 | class ExceptionB | +| 164 | None | +| 166 | None | +| 166 | class ExceptionC | +| 171 | class ExceptionC | +| 172 | class ExceptionA | +| 172 | class ExceptionB | +| 178 | None | +| 178 | class ExceptionA | +| 178 | class ExceptionB | +| 183 | None | +| 183 | class ExceptionC | +| 186 | None | +| 189 | None | +| 190 | class A | +| 196 | None | +| 196 | Unknown | +| 200 | None | +| 200 | Unknown | +| 206 | None | +| 206 | Unknown | +| 209 | Unknown | diff --git a/python/ql/test/library-tests/types/exceptions/LineRaises.ql b/python/ql/test/library-tests/types/exceptions/LineRaises.ql new file mode 100644 index 000000000000..f1f51952d00a --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/LineRaises.ql @@ -0,0 +1,13 @@ + +import python + +from RaisingNode r, string type +where + type = r.getARaisedType().toString() + or + type = "Unknown" and r.raisesUnknownType() + or + not exists(r.getARaisedType()) and + not r.raisesUnknownType() and type = "None" + +select r.getNode().getLocation().getStartLine(), type diff --git a/python/ql/test/library-tests/types/exceptions/Raises.expected b/python/ql/test/library-tests/types/exceptions/Raises.expected new file mode 100644 index 000000000000..bc0ff2e0e3f8 --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/Raises.expected @@ -0,0 +1,37 @@ +| Function coro | Unknown | +| Function f1 | None | +| Function f2 | class ExceptionA | +| Function f5 | class ExceptionA | +| Function f6 | class ExceptionB | +| Function f7 | class ExceptionA | +| Function f7 | class ExceptionB | +| Function f8 | class ExceptionA | +| Function f8a | class ExceptionA | +| Function f8a | class ExceptionB | +| Function f9 | class ExceptionA | +| Function f10 | None | +| Function f11 | class ExceptionC | +| Function f12 | builtin-class LookupError | +| Function f13 | None | +| Function f14 | None | +| Function f15 | None | +| Function f16 | None | +| Function f17 | None | +| Function f18 | builtin-class LookupError | +| Function f19 | builtin-class IndexError | +| Function f20 | None | +| Function f21 | builtin-class IndexError | +| Function f22 | builtin-class IndexError | +| Function f23 | class ExceptionA | +| Function f23 | class ExceptionB | +| Function f23 | class ExceptionC | +| Function f24 | class ExceptionA | +| Function f24 | class ExceptionB | +| Function f25 | class ExceptionA | +| Function f25 | class ExceptionB | +| Function f25 | class ExceptionC | +| Function f26 | class ExceptionB | +| Function f26 | class ExceptionC | +| Function f27 | None | +| Function test_handled | Unknown | +| Function use_coro | Unknown | diff --git a/python/ql/test/library-tests/types/exceptions/Raises.ql b/python/ql/test/library-tests/types/exceptions/Raises.ql new file mode 100644 index 000000000000..b003fd03dfa7 --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/Raises.ql @@ -0,0 +1,13 @@ + +import python + +from PyFunctionObject f, string type +where + type = f.getARaisedType().toString() + or + type = "Unknown" and f.raisesUnknownType() + or + not exists(f.getARaisedType()) and + not f.raisesUnknownType() and type = "None" + +select f.toString(), type \ No newline at end of file diff --git a/python/ql/test/library-tests/types/exceptions/Reraises.expected b/python/ql/test/library-tests/types/exceptions/Reraises.expected new file mode 100644 index 000000000000..48bf8be907fa --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/Reraises.expected @@ -0,0 +1,15 @@ +| 44 | test.py:44:9:44:12 | ControlFlowNode for Pass | class ExceptionA | +| 44 | test.py:44:9:44:12 | ControlFlowNode for Pass | class ExceptionB | +| 53 | test.py:53:9:53:12 | ControlFlowNode for Pass | class ExceptionA | +| 107 | test.py:107:9:107:12 | ControlFlowNode for Pass | builtin-class LookupError | +| 128 | test.py:128:9:128:12 | ControlFlowNode for Pass | builtin-class IndexError | +| 138 | test.py:138:13:138:16 | ControlFlowNode for Pass | class ExceptionA | +| 138 | test.py:138:13:138:16 | ControlFlowNode for Pass | class ExceptionB | +| 138 | test.py:138:13:138:16 | ControlFlowNode for Pass | class ExceptionC | +| 138 | test.py:138:13:138:16 | ControlFlowNode for Pass | class ExceptionC | +| 152 | test.py:152:9:152:12 | ControlFlowNode for Pass | class ExceptionA | +| 152 | test.py:152:9:152:12 | ControlFlowNode for Pass | class ExceptionB | +| 171 | test.py:171:13:171:16 | ControlFlowNode for Pass | class ExceptionC | +| 171 | test.py:171:13:171:16 | ControlFlowNode for Pass | class ExceptionC | +| 172 | test.py:172:9:172:12 | ControlFlowNode for Pass | class ExceptionA | +| 172 | test.py:172:9:172:12 | ControlFlowNode for Pass | class ExceptionB | diff --git a/python/ql/test/library-tests/types/exceptions/Reraises.ql b/python/ql/test/library-tests/types/exceptions/Reraises.ql new file mode 100644 index 000000000000..9edcdf57b4b1 --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/Reraises.ql @@ -0,0 +1,6 @@ + +import python + +from ReraisingNode r + +select r.getLocation().getStartLine(), r, r.getARaisedType().toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/types/exceptions/Viable.expected b/python/ql/test/library-tests/types/exceptions/Viable.expected new file mode 100644 index 000000000000..11e0cbd0ef3c --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/Viable.expected @@ -0,0 +1,25 @@ +| 36 | 37 | f7() | ExceptStmt | class ExceptionB | +| 42 | 44 | f7() | Pass | class ExceptionA | +| 42 | 44 | f7() | Pass | class ExceptionB | +| 49 | 50 | f7() | ExceptStmt | class ExceptionB | +| 49 | 53 | f7() | Pass | class ExceptionA | +| 58 | 59 | f7() | ExceptStmt | class ExceptionB | +| 58 | 62 | f7() | Return | class ExceptionA | +| 67 | 68 | f7() | ExceptStmt | class ExceptionB | +| 67 | 71 | f7() | ExceptionC | class ExceptionA | +| 81 | 82 | Subscript | ExceptStmt | builtin-class KeyError | +| 87 | 88 | Subscript | ExceptStmt | builtin-class IndexError | +| 93 | 94 | Attribute | ExceptStmt | builtin-class AttributeError | +| 105 | 107 | Subscript | Pass | builtin-class LookupError | +| 114 | 115 | f19() | ExceptStmt | builtin-class IndexError | +| 126 | 128 | f19() | Pass | builtin-class IndexError | +| 132 | 134 | f7() | Try | class ExceptionA | +| 132 | 134 | f7() | Try | class ExceptionB | +| 136 | 138 | Raise | Pass | class ExceptionC | +| 146 | 150 | f7() | Pass | class ExceptionA | +| 146 | 150 | f7() | Pass | class ExceptionB | +| 158 | 162 | f7() | Try | class ExceptionA | +| 158 | 162 | f7() | Try | class ExceptionB | +| 166 | 169 | Raise | Pass | class ExceptionC | +| 178 | 181 | f7() | ExceptStmt | class ExceptionA | +| 190 | 191 | Raise | ExceptStmt | class A | diff --git a/python/ql/test/library-tests/types/exceptions/Viable.ql b/python/ql/test/library-tests/types/exceptions/Viable.ql new file mode 100644 index 000000000000..544b0a0d0b6c --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/Viable.ql @@ -0,0 +1,7 @@ + + +import python + +from RaisingNode r, ControlFlowNode n, ClassObject ex +where r.viableExceptionEdge(n, ex) +select r.getLocation().getStartLine(), n.getLocation().getStartLine(), r.getNode().toString(), n.getNode().toString(), ex.toString() diff --git a/python/ql/test/library-tests/types/exceptions/test.py b/python/ql/test/library-tests/types/exceptions/test.py new file mode 100644 index 000000000000..d6b19ca987ac --- /dev/null +++ b/python/ql/test/library-tests/types/exceptions/test.py @@ -0,0 +1,211 @@ + + +class ExceptionA(Exception): + pass + +class ExceptionB(Exception): + pass + +class ExceptionC(Exception): + pass + +def f1(): + pass + +def f2(): + raise ExceptionA() + + + + +def f5(): + f2() + +def f6(): + raise ExceptionB() + raise IOError() + +def f7(): + if x: + raise ExceptionA() + else: + raise ExceptionB() + +def f8(): + try: + f7() + except ExceptionB: + pass + +def f8a(): + try: + f7() + finally: + pass + +def f9(): + try: + try: + f7() + except ExceptionB: + pass + finally: + pass + +def f10(): + try: + try: + f7() + except ExceptionB: + pass + finally: + return + +def f11(): + try: + try: + f7() + except ExceptionB: + pass + finally: + raise ExceptionC() + +def f12(): + x["Hello"] + +def f13(): + x.attr + +def f14(): + try: + x["Hello"] + except KeyError: + pass + +def f15(): + try: + x[1] + except IndexError: + pass + +def f16(): + try: + x.attr + except AttributeError: + pass + +def f17(): + try: + x.attr + except ExceptionA: + pass + +def f18(): + try: + x["Hello"] + finally: + pass + +def f19(): + raise IndexError() + +def f20(): + try: + f19() + except IndexError: + pass + +def f21(): + try: + f19() + except KeyError: + pass + +def f22(): + try: + f19() + finally: + pass + +def f23(): + try: + f7() + finally: + try: + if x: + raise ExceptionC() + finally: + pass + +#Longer basic blocks + +def f24(): + try: + pass + pass + f7() + pass + pass + finally: + pass + pass + pass + +def f25(): + try: + pass + pass + f7() + pass + pass + finally: + try: + pass + if x: + pass + raise ExceptionC() + pass + finally: + pass + pass + pass + pass + +def f26(): + try: + pass + pass + f7() + pass + pass + except ExceptionA: + pass + raise ExceptionC() + +def f27(): + class A(BaseException): + pass + try: + a = A() + raise a + except A: + pass + +def test_handled(): + try: + func() + except (ExceptionA, ExceptionB): + pass + try: + func() + except (ExceptionC, KeyError) as ex: + pass + +def coro(): + yield 0 + raise SpecialException() + +def use_coro(): + yield coro() + reachable + diff --git a/python/ql/test/library-tests/types/functions/Zope.expected b/python/ql/test/library-tests/types/functions/Zope.expected new file mode 100644 index 000000000000..339baf4798a2 --- /dev/null +++ b/python/ql/test/library-tests/types/functions/Zope.expected @@ -0,0 +1 @@ +| Function yes | diff --git a/python/ql/test/library-tests/types/functions/Zope.ql b/python/ql/test/library-tests/types/functions/Zope.ql new file mode 100644 index 000000000000..d3143778b2b4 --- /dev/null +++ b/python/ql/test/library-tests/types/functions/Zope.ql @@ -0,0 +1,6 @@ + +import python +import semmle.python.libraries.Zope + +from ZopeInterfaceMethod f +select f.toString() diff --git a/python/ql/test/library-tests/types/functions/test.py b/python/ql/test/library-tests/types/functions/test.py new file mode 100644 index 000000000000..7e2b24607be9 --- /dev/null +++ b/python/ql/test/library-tests/types/functions/test.py @@ -0,0 +1,12 @@ +import zope.interface + +#ODASA-6062 +class Z(zope.interface.Interface): + + def yes(arg): + pass + +class NotZ(object): + + def no(self): + pass diff --git a/python/ql/test/library-tests/types/functions/zope/__init__.py b/python/ql/test/library-tests/types/functions/zope/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/types/functions/zope/interface.py b/python/ql/test/library-tests/types/functions/zope/interface.py new file mode 100644 index 000000000000..0bad91e9620e --- /dev/null +++ b/python/ql/test/library-tests/types/functions/zope/interface.py @@ -0,0 +1,6 @@ +#Fake zope.interface Module + +class InterfaceClass(object): + pass + +Interface = InterfaceClass() diff --git a/python/ql/test/library-tests/types/properties/Deleters.expected b/python/ql/test/library-tests/types/properties/Deleters.expected new file mode 100644 index 000000000000..1fb91ed794b2 --- /dev/null +++ b/python/ql/test/library-tests/types/properties/Deleters.expected @@ -0,0 +1 @@ +| Property p2 | Function p2 | \ No newline at end of file diff --git a/python/ql/test/library-tests/types/properties/Deleters.ql b/python/ql/test/library-tests/types/properties/Deleters.ql new file mode 100644 index 000000000000..e57f5917e6ec --- /dev/null +++ b/python/ql/test/library-tests/types/properties/Deleters.ql @@ -0,0 +1,5 @@ +import python + +from PythonPropertyObject p + +select p.toString(), p.getDeleter().toString() diff --git a/python/ql/test/library-tests/types/properties/Getters.expected b/python/ql/test/library-tests/types/properties/Getters.expected new file mode 100644 index 000000000000..c451d9c1f38e --- /dev/null +++ b/python/ql/test/library-tests/types/properties/Getters.expected @@ -0,0 +1,3 @@ +| Property p1 | Function p1 | +| Property p2 | Function p2 | +| Property p3 | Function p3 | \ No newline at end of file diff --git a/python/ql/test/library-tests/types/properties/Getters.ql b/python/ql/test/library-tests/types/properties/Getters.ql new file mode 100644 index 000000000000..2d495ccfc2f6 --- /dev/null +++ b/python/ql/test/library-tests/types/properties/Getters.ql @@ -0,0 +1,5 @@ +import python + +from PythonPropertyObject p + +select p.toString(), p.getGetter().toString() diff --git a/python/ql/test/library-tests/types/properties/PythonProperties.expected b/python/ql/test/library-tests/types/properties/PythonProperties.expected new file mode 100644 index 000000000000..4bb0de96c31b --- /dev/null +++ b/python/ql/test/library-tests/types/properties/PythonProperties.expected @@ -0,0 +1,3 @@ +| Property p1 | +| Property p2 | +| Property p3 | \ No newline at end of file diff --git a/python/ql/test/library-tests/types/properties/PythonProperties.ql b/python/ql/test/library-tests/types/properties/PythonProperties.ql new file mode 100644 index 000000000000..91281c321a08 --- /dev/null +++ b/python/ql/test/library-tests/types/properties/PythonProperties.ql @@ -0,0 +1,5 @@ +import python + +from PythonPropertyObject p + +select p.toString() diff --git a/python/ql/test/library-tests/types/properties/Setters.expected b/python/ql/test/library-tests/types/properties/Setters.expected new file mode 100644 index 000000000000..852998fd746c --- /dev/null +++ b/python/ql/test/library-tests/types/properties/Setters.expected @@ -0,0 +1,2 @@ +| Property p1 | Function p1 | +| Property p3 | Function p3_set | \ No newline at end of file diff --git a/python/ql/test/library-tests/types/properties/Setters.ql b/python/ql/test/library-tests/types/properties/Setters.ql new file mode 100644 index 000000000000..4e13e54db630 --- /dev/null +++ b/python/ql/test/library-tests/types/properties/Setters.ql @@ -0,0 +1,5 @@ +import python + +from PythonPropertyObject p + +select p.toString(), p.getSetter().toString() diff --git a/python/ql/test/library-tests/types/properties/properties.py b/python/ql/test/library-tests/types/properties/properties.py new file mode 100644 index 000000000000..4f092454ac1b --- /dev/null +++ b/python/ql/test/library-tests/types/properties/properties.py @@ -0,0 +1,29 @@ +class C(object): + + @property + def p1(self): + return 1 + + @p1.setter + def p1(self, val): + pass + + @property + def p2(self): + return 1 + + @p2.deleter + def p2(self, val): + pass + + def p3(self): + return 1 + + p3 = property(p3) + + def p3_set(self, val): + pass + + p3 = p3.setter(p3_set) + + \ No newline at end of file diff --git a/python/ql/test/library-tests/variables/definitions/test.expected b/python/ql/test/library-tests/variables/definitions/test.expected new file mode 100644 index 000000000000..dc853ee7f2f9 --- /dev/null +++ b/python/ql/test/library-tests/variables/definitions/test.expected @@ -0,0 +1,4 @@ +| 3 | 5 | ControlFlowNode for fail5 | +| 4 | 5 | ControlFlowNode for Tuple | +| 4 | 5 | ControlFlowNode for x | +| 4 | 8 | ControlFlowNode for y | \ No newline at end of file diff --git a/python/ql/test/library-tests/variables/definitions/test.py b/python/ql/test/library-tests/variables/definitions/test.py new file mode 100644 index 000000000000..0695a275b3c3 --- /dev/null +++ b/python/ql/test/library-tests/variables/definitions/test.py @@ -0,0 +1,5 @@ + +#ODASA-4153 +def fail5(t): + x, y = t + return x diff --git a/python/ql/test/library-tests/variables/definitions/test.ql b/python/ql/test/library-tests/variables/definitions/test.ql new file mode 100644 index 000000000000..9abee816b3ad --- /dev/null +++ b/python/ql/test/library-tests/variables/definitions/test.ql @@ -0,0 +1,5 @@ +import python + +from DefinitionNode d + +select d.getLocation().getStartLine(), d.getLocation().getStartColumn(), d.toString() diff --git a/python/ql/test/library-tests/variables/scopes/free.expected b/python/ql/test/library-tests/variables/scopes/free.expected new file mode 100644 index 000000000000..3277eb55dd65 --- /dev/null +++ b/python/ql/test/library-tests/variables/scopes/free.expected @@ -0,0 +1,9 @@ +| Local Variable local2 | Function func2 | Function inner1 | +| Local Variable local4 | Function func3 | Function inner2 | +| Local Variable local4 | Function func3 | Function inner_outer | +| Local Variable local5 | Function inner_outer | Function inner2 | +| Local Variable param4 | Function func3 | Function inner2 | +| Local Variable param4 | Function func3 | Function inner_outer | +| Local Variable param5 | Function func3 | Function inner_outer | +| Local Variable param6 | Function func4 | Function meth_inner | +| Local Variable z | Function func6 | Function listcomp | diff --git a/python/ql/test/library-tests/variables/scopes/free.ql b/python/ql/test/library-tests/variables/scopes/free.ql new file mode 100644 index 000000000000..1e15bb3a312a --- /dev/null +++ b/python/ql/test/library-tests/variables/scopes/free.ql @@ -0,0 +1,7 @@ +import python + +from LocalVariable v, Scope inner +where v.escapes() and inner = v.getAnAccess().getScope() and +inner != v.getScope() +select v.toString(), v.getScope().toString(), inner.toString() + diff --git a/python/ql/test/library-tests/variables/scopes/globals.expected b/python/ql/test/library-tests/variables/scopes/globals.expected new file mode 100644 index 000000000000..c0ed16f7582b --- /dev/null +++ b/python/ql/test/library-tests/variables/scopes/globals.expected @@ -0,0 +1,17 @@ +| Global Variable C | Module test | +| Global Variable __name__ | Module test | +| Global Variable __package__ | Module test | +| Global Variable base | Module test | +| Global Variable func0 | Module test | +| Global Variable func1 | Module test | +| Global Variable func2 | Module test | +| Global Variable func3 | Module test | +| Global Variable func4 | Module test | +| Global Variable func5 | Module test | +| Global Variable func6 | Module test | +| Global Variable global0 | Module test | +| Global Variable global1 | Module test | +| Global Variable global_local | Module test | +| Global Variable range | Module test | +| Global Variable seq | Module test | +| Global Variable use_in_loop | Module test | diff --git a/python/ql/test/library-tests/variables/scopes/globals.ql b/python/ql/test/library-tests/variables/scopes/globals.ql new file mode 100644 index 000000000000..8d200aa81b04 --- /dev/null +++ b/python/ql/test/library-tests/variables/scopes/globals.ql @@ -0,0 +1,5 @@ +import python + +from GlobalVariable l +select l.toString(), l.getScope().toString() + diff --git a/python/ql/test/library-tests/variables/scopes/locals.expected b/python/ql/test/library-tests/variables/scopes/locals.expected new file mode 100644 index 000000000000..1a662bdb6716 --- /dev/null +++ b/python/ql/test/library-tests/variables/scopes/locals.expected @@ -0,0 +1,34 @@ +| Local Variable .0 | test.py:45:12:45:27 | Function listcomp | fast | +| Local Variable .0 | test.py:48:12:48:29 | Function listcomp | fast | +| Local Variable .0 | test.py:52:5:52:25 | Function listcomp | fast | +| Local Variable Local | test.py:38:1:38:18 | Function func4 | fast | +| Local Variable class_local | test.py:30:1:30:14 | Class C | name | +| Local Variable inner1 | test.py:15:1:15:12 | Function func2 | fast | +| Local Variable inner2 | test.py:24:5:24:22 | Function inner_outer | fast | +| Local Variable inner_outer | test.py:22:1:22:26 | Function func3 | fast | +| Local Variable local0 | test.py:8:1:8:12 | Function func1 | fast | +| Local Variable local1 | test.py:8:1:8:12 | Function func1 | fast | +| Local Variable local2 | test.py:15:1:15:12 | Function func2 | fast | +| Local Variable local3 | test.py:17:5:17:23 | Function inner1 | fast | +| Local Variable local4 | test.py:22:1:22:26 | Function func3 | fast | +| Local Variable local5 | test.py:24:5:24:22 | Function inner_outer | fast | +| Local Variable meth | test.py:30:1:30:14 | Class C | name | +| Local Variable meth_inner | test.py:39:5:39:16 | Class Local | name | +| Local Variable mlocal | test.py:34:5:34:19 | Function meth | fast | +| Local Variable param0 | test.py:5:1:5:26 | Function func0 | fast | +| Local Variable param1 | test.py:5:1:5:26 | Function func0 | fast | +| Local Variable param2 | test.py:17:5:17:23 | Function inner1 | fast | +| Local Variable param3 | test.py:25:9:25:27 | Function inner2 | fast | +| Local Variable param4 | test.py:22:1:22:26 | Function func3 | fast | +| Local Variable param5 | test.py:22:1:22:26 | Function func3 | fast | +| Local Variable param6 | test.py:38:1:38:18 | Function func4 | fast | +| Local Variable self | test.py:34:5:34:19 | Function meth | fast | +| Local Variable self | test.py:40:9:40:29 | Function meth_inner | fast | +| Local Variable seq | test.py:44:1:44:15 | Function func5 | fast | +| Local Variable seq | test.py:51:1:51:21 | Function use_in_loop | fast | +| Local Variable v | test.py:51:1:51:21 | Function use_in_loop | fast | +| Local Variable v | test.py:52:5:52:25 | Function listcomp | fast | +| Local Variable x | test.py:45:12:45:27 | Function listcomp | fast | +| Local Variable y | test.py:47:1:47:16 | Function func6 | fast | +| Local Variable y | test.py:48:12:48:29 | Function listcomp | fast | +| Local Variable z | test.py:47:1:47:16 | Function func6 | fast | diff --git a/python/ql/test/library-tests/variables/scopes/locals.ql b/python/ql/test/library-tests/variables/scopes/locals.ql new file mode 100644 index 000000000000..264c5e9b7d1f --- /dev/null +++ b/python/ql/test/library-tests/variables/scopes/locals.ql @@ -0,0 +1,10 @@ +import python + +from LocalVariable l, string kind +where +l instanceof FastLocalVariable and kind = "fast" +or +l instanceof NameLocalVariable and kind = "name" + +select l, l.getScope(), kind + diff --git a/python/ql/test/library-tests/variables/scopes/lookup.expected b/python/ql/test/library-tests/variables/scopes/lookup.expected new file mode 100644 index 000000000000..2418f1d92834 --- /dev/null +++ b/python/ql/test/library-tests/variables/scopes/lookup.expected @@ -0,0 +1,34 @@ +| 6 | ControlFlowNode for param0 | local | +| 6 | ControlFlowNode for param1 | local | +| 12 | ControlFlowNode for global_local | global | +| 13 | ControlFlowNode for global1 | global | +| 13 | ControlFlowNode for local0 | local | +| 13 | ControlFlowNode for local1 | local | +| 18 | ControlFlowNode for local2 | non-local | +| 19 | ControlFlowNode for local3 | local | +| 20 | ControlFlowNode for inner1 | local | +| 26 | ControlFlowNode for local4 | non-local | +| 26 | ControlFlowNode for local5 | non-local | +| 26 | ControlFlowNode for param3 | local | +| 26 | ControlFlowNode for param4 | non-local | +| 28 | ControlFlowNode for inner2 | local | +| 28 | ControlFlowNode for local4 | non-local | +| 28 | ControlFlowNode for param4 | non-local | +| 28 | ControlFlowNode for param5 | non-local | +| 30 | ControlFlowNode for base | global | +| 35 | ControlFlowNode for self | local | +| 36 | ControlFlowNode for mlocal | local | +| 41 | ControlFlowNode for param6 | non-local | +| 42 | ControlFlowNode for Local | local | +| 45 | ControlFlowNode for .0 | local | +| 45 | ControlFlowNode for seq | local | +| 45 | ControlFlowNode for x | local | +| 48 | ControlFlowNode for .0 | local | +| 48 | ControlFlowNode for seq | global | +| 48 | ControlFlowNode for y | local | +| 48 | ControlFlowNode for z | non-local | +| 52 | ControlFlowNode for .0 | local | +| 52 | ControlFlowNode for range | global | +| 52 | ControlFlowNode for v | local | +| 53 | ControlFlowNode for seq | local | +| 54 | ControlFlowNode for v | local | diff --git a/python/ql/test/library-tests/variables/scopes/lookup.ql b/python/ql/test/library-tests/variables/scopes/lookup.ql new file mode 100644 index 000000000000..c7a776c7caa2 --- /dev/null +++ b/python/ql/test/library-tests/variables/scopes/lookup.ql @@ -0,0 +1,16 @@ +import python + +from NameNode n, string l +where +n.isLoad() and ( + n.isGlobal() and l = "global" + or + n.isLocal() and l = "local" + or + n.isNonLocal() and l = "non-local" + or + not n.isGlobal() and not n.isLocal() and + not n.isNonLocal() and + l = "none" +) +select n.getLocation().getStartLine(), n.toString(), l diff --git a/python/ql/test/library-tests/variables/scopes/scopes.expected b/python/ql/test/library-tests/variables/scopes/scopes.expected new file mode 100644 index 000000000000..cf4a6edbfbb2 --- /dev/null +++ b/python/ql/test/library-tests/variables/scopes/scopes.expected @@ -0,0 +1,51 @@ +| Global Variable C | test.py:0:0:0:0 | Module test | +| Global Variable __name__ | test.py:0:0:0:0 | Module test | +| Global Variable __package__ | test.py:0:0:0:0 | Module test | +| Global Variable base | test.py:0:0:0:0 | Module test | +| Global Variable func0 | test.py:0:0:0:0 | Module test | +| Global Variable func1 | test.py:0:0:0:0 | Module test | +| Global Variable func2 | test.py:0:0:0:0 | Module test | +| Global Variable func3 | test.py:0:0:0:0 | Module test | +| Global Variable func4 | test.py:0:0:0:0 | Module test | +| Global Variable func5 | test.py:0:0:0:0 | Module test | +| Global Variable func6 | test.py:0:0:0:0 | Module test | +| Global Variable global0 | test.py:0:0:0:0 | Module test | +| Global Variable global1 | test.py:0:0:0:0 | Module test | +| Global Variable global_local | test.py:0:0:0:0 | Module test | +| Global Variable range | test.py:0:0:0:0 | Module test | +| Global Variable seq | test.py:0:0:0:0 | Module test | +| Global Variable use_in_loop | test.py:0:0:0:0 | Module test | +| Local Variable .0 | test.py:45:12:45:27 | Function listcomp | +| Local Variable .0 | test.py:48:12:48:29 | Function listcomp | +| Local Variable .0 | test.py:52:5:52:25 | Function listcomp | +| Local Variable Local | test.py:38:1:38:18 | Function func4 | +| Local Variable class_local | test.py:30:1:30:14 | Class C | +| Local Variable inner1 | test.py:15:1:15:12 | Function func2 | +| Local Variable inner2 | test.py:24:5:24:22 | Function inner_outer | +| Local Variable inner_outer | test.py:22:1:22:26 | Function func3 | +| Local Variable local0 | test.py:8:1:8:12 | Function func1 | +| Local Variable local1 | test.py:8:1:8:12 | Function func1 | +| Local Variable local2 | test.py:15:1:15:12 | Function func2 | +| Local Variable local3 | test.py:17:5:17:23 | Function inner1 | +| Local Variable local4 | test.py:22:1:22:26 | Function func3 | +| Local Variable local5 | test.py:24:5:24:22 | Function inner_outer | +| Local Variable meth | test.py:30:1:30:14 | Class C | +| Local Variable meth_inner | test.py:39:5:39:16 | Class Local | +| Local Variable mlocal | test.py:34:5:34:19 | Function meth | +| Local Variable param0 | test.py:5:1:5:26 | Function func0 | +| Local Variable param1 | test.py:5:1:5:26 | Function func0 | +| Local Variable param2 | test.py:17:5:17:23 | Function inner1 | +| Local Variable param3 | test.py:25:9:25:27 | Function inner2 | +| Local Variable param4 | test.py:22:1:22:26 | Function func3 | +| Local Variable param5 | test.py:22:1:22:26 | Function func3 | +| Local Variable param6 | test.py:38:1:38:18 | Function func4 | +| Local Variable self | test.py:34:5:34:19 | Function meth | +| Local Variable self | test.py:40:9:40:29 | Function meth_inner | +| Local Variable seq | test.py:44:1:44:15 | Function func5 | +| Local Variable seq | test.py:51:1:51:21 | Function use_in_loop | +| Local Variable v | test.py:51:1:51:21 | Function use_in_loop | +| Local Variable v | test.py:52:5:52:25 | Function listcomp | +| Local Variable x | test.py:45:12:45:27 | Function listcomp | +| Local Variable y | test.py:47:1:47:16 | Function func6 | +| Local Variable y | test.py:48:12:48:29 | Function listcomp | +| Local Variable z | test.py:47:1:47:16 | Function func6 | diff --git a/python/ql/test/library-tests/variables/scopes/scopes.ql b/python/ql/test/library-tests/variables/scopes/scopes.ql new file mode 100644 index 000000000000..b87a45c99393 --- /dev/null +++ b/python/ql/test/library-tests/variables/scopes/scopes.ql @@ -0,0 +1,6 @@ + +import python + +from Variable v, Scope s +where v.getScope() = s +select v, s diff --git a/python/ql/test/library-tests/variables/scopes/test.py b/python/ql/test/library-tests/variables/scopes/test.py new file mode 100644 index 000000000000..940576d44dfe --- /dev/null +++ b/python/ql/test/library-tests/variables/scopes/test.py @@ -0,0 +1,54 @@ + +global0 = 0 +global1 = 1 + +def func0(param0, param1): + return param0 + param1 + +def func1(): + global global0, global_local + local0 = 0 + local1 = 1 + global_local + global0 = local0 + local1 + global1 + +def func2(): + local2 = 2 + def inner1(param2): + local3 = local2 + return local3 + return inner1 + +def func3(param4, param5): + local4 = 4 + def inner_outer(): + def inner2(param3): + return local5 + local4 + param3 + param4 + local5 = 3 + return inner2(local4 + param4 + param5) + +class C(base): + + class_local = 7 + + def meth(self): + mlocal = self + return mlocal + +def func4(param6): + class Local: + def meth_inner(self): + return param6 + return Local() + +def func5(seq): + return [x for x in seq] + +def func6(y, z): + return [y+z for y in seq] + +#FP observed in sembuild +def use_in_loop(seq): + [v for v in range(3)] + for v in seq: + v #x redefined -- fine in 2 and 3. diff --git a/python/ql/test/queries.xml b/python/ql/test/queries.xml new file mode 100644 index 000000000000..27449f34263b --- /dev/null +++ b/python/ql/test/queries.xml @@ -0,0 +1 @@ + diff --git a/python/ql/test/query-tests/Classes/Arguments/WrongNameForArgumentInClassInstantiation.expected b/python/ql/test/query-tests/Classes/Arguments/WrongNameForArgumentInClassInstantiation.expected new file mode 100644 index 000000000000..cf89ddfc44f0 --- /dev/null +++ b/python/ql/test/query-tests/Classes/Arguments/WrongNameForArgumentInClassInstantiation.expected @@ -0,0 +1,4 @@ +| wrong_arguments.py:65:1:65:7 | F0() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ | +| wrong_arguments.py:66:1:66:7 | F1() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ | +| wrong_arguments.py:67:1:67:12 | F2() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:12:5:12:30 | Function __init__ | F2.__init__ | +| wrong_arguments.py:92:1:92:27 | F6() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ | diff --git a/python/ql/test/query-tests/Classes/Arguments/WrongNameForArgumentInClassInstantiation.qlref b/python/ql/test/query-tests/Classes/Arguments/WrongNameForArgumentInClassInstantiation.qlref new file mode 100644 index 000000000000..408766dcbf4d --- /dev/null +++ b/python/ql/test/query-tests/Classes/Arguments/WrongNameForArgumentInClassInstantiation.qlref @@ -0,0 +1 @@ +Classes/WrongNameForArgumentInClassInstantiation.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/Arguments/WrongNumberArgumentsInClassInstantiation.expected b/python/ql/test/query-tests/Classes/Arguments/WrongNumberArgumentsInClassInstantiation.expected new file mode 100644 index 000000000000..b0c156dc8673 --- /dev/null +++ b/python/ql/test/query-tests/Classes/Arguments/WrongNumberArgumentsInClassInstantiation.expected @@ -0,0 +1,15 @@ +| wrong_arguments.py:37:1:37:4 | F0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ | +| wrong_arguments.py:38:1:38:4 | F1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ | +| wrong_arguments.py:39:1:39:4 | F2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:12:5:12:30 | Function __init__ | F2.__init__ | +| wrong_arguments.py:40:1:40:4 | F3() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:16:5:16:40 | Function __init__ | F3.__init__ | +| wrong_arguments.py:41:1:41:4 | F4() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:20:5:20:31 | Function __init__ | F4.__init__ | +| wrong_arguments.py:42:1:42:4 | F5() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:24:5:24:42 | Function __init__ | F5.__init__ | +| wrong_arguments.py:43:1:43:5 | F6() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ | +| wrong_arguments.py:44:1:44:7 | F7() | Call to $@ with too few arguments; should be no fewer than 3. | wrong_arguments.py:32:5:32:33 | Function __init__ | F7.__init__ | +| wrong_arguments.py:48:1:48:7 | F0() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ | +| wrong_arguments.py:49:1:49:9 | F1() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ | +| wrong_arguments.py:50:1:50:9 | F5() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:24:5:24:42 | Function __init__ | F5.__init__ | +| wrong_arguments.py:51:1:51:9 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ | +| wrong_arguments.py:52:1:52:11 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ | +| wrong_arguments.py:85:1:85:12 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ | +| wrong_arguments.py:86:1:86:7 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ | diff --git a/python/ql/test/query-tests/Classes/Arguments/WrongNumberArgumentsInClassInstantiation.qlref b/python/ql/test/query-tests/Classes/Arguments/WrongNumberArgumentsInClassInstantiation.qlref new file mode 100644 index 000000000000..4fdda20e1636 --- /dev/null +++ b/python/ql/test/query-tests/Classes/Arguments/WrongNumberArgumentsInClassInstantiation.qlref @@ -0,0 +1 @@ +Classes/WrongNumberArgumentsInClassInstantiation.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/Arguments/wrong_arguments.py b/python/ql/test/query-tests/Classes/Arguments/wrong_arguments.py new file mode 100644 index 000000000000..7363fdebef4e --- /dev/null +++ b/python/ql/test/query-tests/Classes/Arguments/wrong_arguments.py @@ -0,0 +1,93 @@ +# Test cases corresponding to /Expressions/Arguments/wrong_arguments.py + +class F0(object): + def __init__(self, x): + pass + +class F1(object): + def __init__(self, x, y = None): + pass + +class F2(object): + def __init__(self, x, *y): + pass + +class F3(object): + def __init__(self, x, y = None, *z): + pass + +class F4(object): + def __init__(self, x, **y): + pass + +class F5(object): + def __init__(self, x, y = None, **z): + pass + +class F6(object): + def __init__(self, x, y): + pass + +class F7(object): + def __init__(self, x, y, z): + pass + +# Too few arguments + +F0() +F1() +F2() +F3() +F4() +F5() +F6(1) +F7(1,2) + +#Too many arguments + +F0(1,2) +F1(1,2,3) +F5(1,2,3) +F6(1,2,3) +F6(1,2,3,4) + +#OK + +#Not too few +F7(*t) + +#Not too many + +F2(1,2,3,4,5,6) + + +#Illegal name +F0(y=1) +F1(z=1) +F2(x=0, y=1) + + +#Ok name +F0(x=0) +F1(x=0, y=1) +F4(q=4) + +#This is correct, but a bit weird. +F6(**{'x':1, 'y':2}) + +t2 = (1,2) +t3 = (1,2,3) + +#Ok +f(*t2) + +#Too many +F6(*(1,2,3)) +F6(*t3) + +#Ok +F6(**{'x':1, 'y':2}) + +#Illegal name +F6(**{'x':1, 'y':2, 'z':3}) + diff --git a/python/ql/test/query-tests/Classes/conflicting/ConflictingAttributesInBaseClasses.expected b/python/ql/test/query-tests/Classes/conflicting/ConflictingAttributesInBaseClasses.expected new file mode 100644 index 000000000000..6aca2a29c461 --- /dev/null +++ b/python/ql/test/query-tests/Classes/conflicting/ConflictingAttributesInBaseClasses.expected @@ -0,0 +1,2 @@ +| test.py:26:1:26:25 | class Conflict | Base classes have conflicting values for attribute 'attr': $@ and $@. | file://:Compiled Code:0:0:0:0 | int 1 | int 1 | test.py:20:13:20:16 | Tuple | Tuple | +| test.py:26:1:26:25 | class Conflict | Base classes have conflicting values for attribute 'meth': $@ and $@. | test.py:14:5:14:19 | Function meth | Function meth | test.py:22:5:22:19 | Function meth | Function meth | diff --git a/python/ql/test/query-tests/Classes/conflicting/ConflictingAttributesInBaseClasses.qlref b/python/ql/test/query-tests/Classes/conflicting/ConflictingAttributesInBaseClasses.qlref new file mode 100644 index 000000000000..3d6fa6534c5c --- /dev/null +++ b/python/ql/test/query-tests/Classes/conflicting/ConflictingAttributesInBaseClasses.qlref @@ -0,0 +1 @@ +Classes/ConflictingAttributesInBaseClasses.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/conflicting/odasa6643.py b/python/ql/test/query-tests/Classes/conflicting/odasa6643.py new file mode 100644 index 000000000000..30efea5df359 --- /dev/null +++ b/python/ql/test/query-tests/Classes/conflicting/odasa6643.py @@ -0,0 +1,17 @@ +#This code has conflicting attributes, +#but the documentation in the standard library tells you do it this way :( + +#See https://discuss.lgtm.com/t/warning-on-normal-use-of-python-socketserver-mixins/677 + +class ThreadingMixIn(object): + + def process_request(selfself, req): + pass + +class HTTPServer(object): + + def process_request(selfself, req): + pass + +class _ThreadingSimpleServer(ThreadingMixIn, HTTPServer): + pass diff --git a/python/ql/test/query-tests/Classes/conflicting/test.py b/python/ql/test/query-tests/Classes/conflicting/test.py new file mode 100644 index 000000000000..624cea77ce5b --- /dev/null +++ b/python/ql/test/query-tests/Classes/conflicting/test.py @@ -0,0 +1,52 @@ + + +#Conflicting attributes in base classes + +class Common(object): + ok1 = None + + def ok2(self): + return None + +class CB1(Common): + attr = 1 + + def meth(self): + pass + + +class CB2(Common): + + attr = (x, y) + + def meth(self): + return 0 + + +class Conflict(CB1, CB2): + pass + +class Override1(Common): + + def ok2(self): + return 1 + +class Override2(Common): + + def ok2(self): + return 2 + +class OK1(Override1, Override2): + + def ok2(self): + return 3 + + +class Override3(Override2): + pass + +class OK2(Override1, Override3): + + def ok2(self): + return 4 + diff --git a/python/ql/test/query-tests/Classes/descriptors/MutatingDescriptor.expected b/python/ql/test/query-tests/Classes/descriptors/MutatingDescriptor.expected new file mode 100644 index 000000000000..ae4d733f7adb --- /dev/null +++ b/python/ql/test/query-tests/Classes/descriptors/MutatingDescriptor.expected @@ -0,0 +1 @@ +| test.py:10:9:10:19 | Attribute | Mutation of descriptor $@ object may lead to action-at-a-distance effects or race conditions for properties. | test.py:3:1:3:33 | class MutatingDescriptor | MutatingDescriptor | diff --git a/python/ql/test/query-tests/Classes/descriptors/MutatingDescriptor.qlref b/python/ql/test/query-tests/Classes/descriptors/MutatingDescriptor.qlref new file mode 100644 index 000000000000..08449405ad61 --- /dev/null +++ b/python/ql/test/query-tests/Classes/descriptors/MutatingDescriptor.qlref @@ -0,0 +1 @@ +Classes/MutatingDescriptor.ql diff --git a/python/ql/test/query-tests/Classes/descriptors/test.py b/python/ql/test/query-tests/Classes/descriptors/test.py new file mode 100644 index 000000000000..ba8da34839ec --- /dev/null +++ b/python/ql/test/query-tests/Classes/descriptors/test.py @@ -0,0 +1,14 @@ + +#This is prone to strange side effects and race conditions. +class MutatingDescriptor(object): + + def __init__(self, func): + self.my_func = func + + def __get__(self, obj, obj_type): + #Modified state is visible to all instances of C that might call "show". + self.my_obj = obj + return self + + def __call__(self, *args): + return self.my_func(self.my_obj, *args) \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/equals-attr/DefineEqualsWhenAddingAttributes.expected b/python/ql/test/query-tests/Classes/equals-attr/DefineEqualsWhenAddingAttributes.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Classes/equals-attr/DefineEqualsWhenAddingAttributes.qlref b/python/ql/test/query-tests/Classes/equals-attr/DefineEqualsWhenAddingAttributes.qlref new file mode 100644 index 000000000000..e542a6176ad4 --- /dev/null +++ b/python/ql/test/query-tests/Classes/equals-attr/DefineEqualsWhenAddingAttributes.qlref @@ -0,0 +1 @@ +Classes/DefineEqualsWhenAddingAttributes.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/equals-attr/test.py b/python/ql/test/query-tests/Classes/equals-attr/test.py new file mode 100644 index 000000000000..148459d08712 --- /dev/null +++ b/python/ql/test/query-tests/Classes/equals-attr/test.py @@ -0,0 +1,16 @@ + +class GenericEquality(object): + + def __eq__(self, other): + if type(other) is not type(self): + return False + for attr in self.__dict__: + if getattr(other, attr) != getattr(self, attr): + return False + return True + + +class AddAttributes(GenericEquality): + + def __init__(self, args): + self.a, self.b = args diff --git a/python/ql/test/query-tests/Classes/equals-hash/DefineEqualsWhenAddingFields.expected b/python/ql/test/query-tests/Classes/equals-hash/DefineEqualsWhenAddingFields.expected new file mode 100644 index 000000000000..dcdb8992b18b --- /dev/null +++ b/python/ql/test/query-tests/Classes/equals-hash/DefineEqualsWhenAddingFields.expected @@ -0,0 +1 @@ +| attr_eq_test.py:21:1:21:27 | class BadColorPoint | The class 'BadColorPoint' does not override $@, but adds the new attribute $@. | attr_eq_test.py:10:5:10:28 | Function __eq__ | '__eq__' | attr_eq_test.py:25:9:25:19 | Attribute | _color | diff --git a/python/ql/test/query-tests/Classes/equals-hash/DefineEqualsWhenAddingFields.qlref b/python/ql/test/query-tests/Classes/equals-hash/DefineEqualsWhenAddingFields.qlref new file mode 100644 index 000000000000..e542a6176ad4 --- /dev/null +++ b/python/ql/test/query-tests/Classes/equals-hash/DefineEqualsWhenAddingFields.qlref @@ -0,0 +1 @@ +Classes/DefineEqualsWhenAddingAttributes.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/equals-hash/attr_eq_test.py b/python/ql/test/query-tests/Classes/equals-hash/attr_eq_test.py new file mode 100644 index 000000000000..e1e545fe9ef5 --- /dev/null +++ b/python/ql/test/query-tests/Classes/equals-hash/attr_eq_test.py @@ -0,0 +1,107 @@ +class Point(object): + + def __init__(self, x, y): + self._x = x + self._y = y + + def __repr__(self): + return 'Point(%r, %r)' % (self._x, self._y) + + def __eq__(self, other): + if not isinstance(other, Point): + return False + return self._x == other._x and self._y == other._y + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self._x, self._y)) + +class BadColorPoint(Point): + + def __init__(self, x, y, color): + Point.__init__(self, x, y) + self._color = color + + def __repr__(self): + return 'ColorPoint(%r, %r)' % (self._x, self._y, self._color) + +class GoodColorPoint(Point): + + def __init__(self, x, y, color): + Point.__init__(self, x, y) + self._color = color + + def __repr__(self): + return 'ColorPoint(%r, %r)' % (self._x, self._y, self._color) + + def __eq__(self, other): + if not isinstance(other, GoodColorPoint): + return False + return Point.__eq__(self, other) and self._color == other._color + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self._x, self._y, self._color)) + +class GenericPoint(object): + + def __init__(self, x, y): + self._x = x + self._y = y + + def __repr__(self): + return 'Point(%r, %r)' % (self._x, self._y) + + def __eq__(self, other): + return self.__class__ == other.__class__ and self.__dict__ == other.__dict__ + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self._x, self._y)) + +class GoodGenericColorPoint(GenericPoint): + + def __init__(self, x, y, color): + GenericPoint.__init__(self, x, y) + self._color = color + +class RedefineEq(object): + + def __init__(self, x, y): + self._x = x + self._y = y + + def __eq__(self, other): + return self is other + +class OK1(RedefineEq): + + def __init__(self, x, y, z): + RedefineEq.__init__(self, x, y) + self.z = z + +class OK2(GenericPoint): + + def __init__(self, x, y, color): + GenericPoint.__init__(self, x, y) + self._color = color + + def __eq__(self, other): + return self is other + +class ExpectingAttribute(object): + + def __eq__(self, other): + return self.x == other.x + +class OK3(ExpectingAttribute): + + def __init__(self): + self.x = 4 + diff --git a/python/ql/test/query-tests/Classes/incomplete-ordering/IncompleteOrdering.expected b/python/ql/test/query-tests/Classes/incomplete-ordering/IncompleteOrdering.expected new file mode 100644 index 000000000000..cac33e836a96 --- /dev/null +++ b/python/ql/test/query-tests/Classes/incomplete-ordering/IncompleteOrdering.expected @@ -0,0 +1 @@ +| incomplete_ordering.py:3:1:3:26 | class PartOrdered | Class PartOrdered implements $@, but does not implement __le__ or __gt__ or __ge__. | incomplete_ordering.py:13:5:13:28 | Function __lt__ | __lt__ | diff --git a/python/ql/test/query-tests/Classes/incomplete-ordering/IncompleteOrdering.qlref b/python/ql/test/query-tests/Classes/incomplete-ordering/IncompleteOrdering.qlref new file mode 100644 index 000000000000..3387dad807a7 --- /dev/null +++ b/python/ql/test/query-tests/Classes/incomplete-ordering/IncompleteOrdering.qlref @@ -0,0 +1 @@ +Classes/IncompleteOrdering.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/incomplete-ordering/incomplete_ordering.py b/python/ql/test/query-tests/Classes/incomplete-ordering/incomplete_ordering.py new file mode 100644 index 000000000000..3c7514d7a838 --- /dev/null +++ b/python/ql/test/query-tests/Classes/incomplete-ordering/incomplete_ordering.py @@ -0,0 +1,18 @@ +#Incomplete ordering + +class PartOrdered(object): + def __eq__(self, other): + return self is other + + def __ne__(self, other): + return self is not other + + def __hash__(self): + return id(self) + + def __lt__(self, other): + return False + +#Don't blame a sub-class for super-class's sins. +class DerivedPartOrdered(PartOrdered): + pass \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/init-calls-subclass-method/InitCallsSubclassMethod.expected b/python/ql/test/query-tests/Classes/init-calls-subclass-method/InitCallsSubclassMethod.expected new file mode 100644 index 000000000000..d3cde45c1ff2 --- /dev/null +++ b/python/ql/test/query-tests/Classes/init-calls-subclass-method/InitCallsSubclassMethod.expected @@ -0,0 +1 @@ +| init_calls_subclass.py:7:9:7:24 | Attribute() | Call to self.$@ in __init__ method, which is overridden by $@. | init_calls_subclass.py:10:5:10:26 | Function set_up | set_up | init_calls_subclass.py:19:5:19:26 | Function set_up | method Sub.set_up | diff --git a/python/ql/test/query-tests/Classes/init-calls-subclass-method/InitCallsSubclassMethod.qlref b/python/ql/test/query-tests/Classes/init-calls-subclass-method/InitCallsSubclassMethod.qlref new file mode 100644 index 000000000000..f820a30b11a2 --- /dev/null +++ b/python/ql/test/query-tests/Classes/init-calls-subclass-method/InitCallsSubclassMethod.qlref @@ -0,0 +1 @@ +Classes/InitCallsSubclassMethod.ql diff --git a/python/ql/test/query-tests/Classes/init-calls-subclass-method/init_calls_subclass.py b/python/ql/test/query-tests/Classes/init-calls-subclass-method/init_calls_subclass.py new file mode 100644 index 000000000000..3248f8d83034 --- /dev/null +++ b/python/ql/test/query-tests/Classes/init-calls-subclass-method/init_calls_subclass.py @@ -0,0 +1,22 @@ +#Superclass __init__ calls subclass method + +class Super(object): + + def __init__(self, arg): + self._state = "Not OK" + self.set_up(arg) + self._state = "OK" + + def set_up(self, arg): + "Do some set up" + +class Sub(Super): + + def __init__(self, arg): + Super.__init__(self, arg) + self.important_state = "OK" + + def set_up(self, arg): + Super.set_up(self, arg) + "Do some more set up" # Dangerous as self._state is "Not OK" and + # self.important_state is uninitialized \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/missing-del/MissingCallToDel.expected b/python/ql/test/query-tests/Classes/missing-del/MissingCallToDel.expected new file mode 100644 index 000000000000..7f080b1d729b --- /dev/null +++ b/python/ql/test/query-tests/Classes/missing-del/MissingCallToDel.expected @@ -0,0 +1 @@ +| missing_del.py:12:1:12:13 | class X3 | Class X3 may not be cleaned up properly as $@ is not called during deletion. | missing_del.py:9:5:9:22 | Function __del__ | method X2.__del__ | diff --git a/python/ql/test/query-tests/Classes/missing-del/MissingCallToDel.qlref b/python/ql/test/query-tests/Classes/missing-del/MissingCallToDel.qlref new file mode 100644 index 000000000000..8bf1811d0fab --- /dev/null +++ b/python/ql/test/query-tests/Classes/missing-del/MissingCallToDel.qlref @@ -0,0 +1 @@ +Classes/MissingCallToDel.ql diff --git a/python/ql/test/query-tests/Classes/missing-del/missing_del.py b/python/ql/test/query-tests/Classes/missing-del/missing_del.py new file mode 100644 index 000000000000..5d4e30e681d2 --- /dev/null +++ b/python/ql/test/query-tests/Classes/missing-del/missing_del.py @@ -0,0 +1,15 @@ +#Not calling an __del__ method: +class X1(object): + + def __del__(self): + pass + +class X2(X1): + + def __del__(self): + X1.__del__(self) + +class X3(X2): + + def __del__(self): + X1.__del__(self) diff --git a/python/ql/test/query-tests/Classes/missing-init/MissingCallToInit.expected b/python/ql/test/query-tests/Classes/missing-init/MissingCallToInit.expected new file mode 100644 index 000000000000..6cb92041a630 --- /dev/null +++ b/python/ql/test/query-tests/Classes/missing-init/MissingCallToInit.expected @@ -0,0 +1,3 @@ +| missing_init.py:12:1:12:13 | class B3 | Class B3 may not be initialized properly as $@ is not called from its $@. | missing_init.py:9:5:9:23 | Function __init__ | method B2.__init__ | missing_init.py:14:5:14:23 | Function __init__ | __init__ method | +| missing_init.py:39:1:39:21 | class IUVT | Class IUVT may not be initialized properly as $@ is not called from its $@. | missing_init.py:30:5:30:23 | Function __init__ | method UT.__init__ | missing_init.py:26:5:26:23 | Function __init__ | __init__ method | +| missing_init.py:72:1:72:13 | class AB | Class AB may not be initialized properly as $@ is not called from its $@. | missing_init.py:69:5:69:23 | Function __init__ | method AA.__init__ | missing_init.py:75:5:75:23 | Function __init__ | __init__ method | diff --git a/python/ql/test/query-tests/Classes/missing-init/MissingCallToInit.qlref b/python/ql/test/query-tests/Classes/missing-init/MissingCallToInit.qlref new file mode 100644 index 000000000000..b8635a5f8d92 --- /dev/null +++ b/python/ql/test/query-tests/Classes/missing-init/MissingCallToInit.qlref @@ -0,0 +1 @@ +Classes/MissingCallToInit.ql diff --git a/python/ql/test/query-tests/Classes/missing-init/missing_init.py b/python/ql/test/query-tests/Classes/missing-init/missing_init.py new file mode 100644 index 000000000000..209098050046 --- /dev/null +++ b/python/ql/test/query-tests/Classes/missing-init/missing_init.py @@ -0,0 +1,185 @@ +#Not calling an __init__ method: +class B1(object): + + def __init__(self): + do_something() + +class B2(B1): + + def __init__(self): + B1.__init__(self) + +class B3(B2): + + def __init__(self): + B1.__init__(self) + +#OK if superclass __init__ is builtin as +#builtin classes tend to rely on __new__ +class MyException(Exception): + + def __init__(self): + self.message = "Uninformative" + +#ODASA-4107 +class IUT(object): + def __init__(self): + print("IUT init") + +class UT(object): + def __init__(self): + print("UT init") + +class PU(object): + pass + +class UVT(UT, PU): + pass + +class IUVT(IUT, UVT): + pass + +#False positive observed on LGTM +class M1(object): + def __init__(self): + print("A") + +class M2(object): + pass + +class Mult(M2, M1): + def __init__(self): + super(Mult, self).__init__() # Calls M1.__init__ + +class X: + def __init__(self): + do_something() + +class Y(X): + @decorated + def __init__(self): + X.__init__(self) + +class Z(Y): + def __init__(self): + Y.__init__(self) + +class AA(object): + + def __init__(self): + do_something() + +class AB(AA): + + #Don't call super class init + def __init__(self): + do_something() + +class AC(AB): + + def __init__(self): + #Missing call to AA.__init__ but not AC's fault. + super(AC, self).__init__() + +import six +import abc + +class BA(object): + + def __init__(self): + do_something() + +@six.add_metaclass(abc.ABCMeta) +class BB(BA): + + def __init__(self): + super(BB,self).__init__() + + +@six.add_metaclass(abc.ABCMeta) +class CA(object): + + def __init__(self): + do_something() + +class CB(BA): + + def __init__(self): + super(CB,self).__init__() + +#ODASA-5799 +class DA(object): + + def __init__(self): + do_something() + +class DB(DA): + + class DC(DA): + + def __init__(self): + sup = super(DB.DC, self) + sup.__init__() + +#Simpler variants +class DD(DA): + + def __init__(self): + sup = super(DD, self) + sup.__init__() + +class DE(DA): + + class DF(DA): + + def __init__(self): + sup = super(DE.DF, self).__init__() + +class FA(object): + + def __init__(self): + pass + +class FB(object): + + def __init__(self): + do_something() + +class FC(FA, FB): + + def __init__(self): + #OK to skip call to FA.__init__ as that does nothing. + FB.__init__(self) + +#Potential false positives. + +class ConfusingInit(B1): + + def __init__(self): + super_call = super(ConfusingInit, self).__init__ + super_call() + + +# Library class +import collections + +class G1(collections.Counter): + + def __init__(self): + collections.Counter.__init__(self) + +class G2(G1): + + def __init__(self): + super(G2, self).__init__() + +class G3(collections.Counter): + + def __init__(self): + super(G3, self).__init__() + +class G4(G3): + + def __init__(self): + G3.__init__(self) + diff --git a/python/ql/test/query-tests/Classes/multiple/SuperclassDelCalledMultipleTimes.expected b/python/ql/test/query-tests/Classes/multiple/SuperclassDelCalledMultipleTimes.expected new file mode 100644 index 000000000000..210f24c7525d --- /dev/null +++ b/python/ql/test/query-tests/Classes/multiple/SuperclassDelCalledMultipleTimes.expected @@ -0,0 +1,2 @@ +| multiple_del.py:17:1:17:17 | class Y3 | Class Y3 may not be cleaned up properly as $@ may be called multiple times during destruction. | multiple_del.py:9:5:9:22 | Function __del__ | method Y1.__del__ | +| multiple_del.py:34:1:34:17 | class Z3 | Class Z3 may not be cleaned up properly as $@ may be called multiple times during destruction. | multiple_del.py:26:5:26:22 | Function __del__ | method Z1.__del__ | diff --git a/python/ql/test/query-tests/Classes/multiple/SuperclassDelCalledMultipleTimes.qlref b/python/ql/test/query-tests/Classes/multiple/SuperclassDelCalledMultipleTimes.qlref new file mode 100644 index 000000000000..560d7b7dc416 --- /dev/null +++ b/python/ql/test/query-tests/Classes/multiple/SuperclassDelCalledMultipleTimes.qlref @@ -0,0 +1 @@ +Classes/SuperclassDelCalledMultipleTimes.ql diff --git a/python/ql/test/query-tests/Classes/multiple/SuperclassInitCalledMultipleTimes.expected b/python/ql/test/query-tests/Classes/multiple/SuperclassInitCalledMultipleTimes.expected new file mode 100644 index 000000000000..04ce8c0f3730 --- /dev/null +++ b/python/ql/test/query-tests/Classes/multiple/SuperclassInitCalledMultipleTimes.expected @@ -0,0 +1,2 @@ +| multiple_init.py:17:1:17:17 | class C3 | Class C3 may not be initialized properly as $@ may be called multiple times during initialization. | multiple_init.py:9:5:9:23 | Function __init__ | method C1.__init__ | +| multiple_init.py:34:1:34:17 | class D3 | Class D3 may not be initialized properly as $@ may be called multiple times during initialization. | multiple_init.py:26:5:26:23 | Function __init__ | method D1.__init__ | diff --git a/python/ql/test/query-tests/Classes/multiple/SuperclassInitCalledMultipleTimes.qlref b/python/ql/test/query-tests/Classes/multiple/SuperclassInitCalledMultipleTimes.qlref new file mode 100644 index 000000000000..042ddb76904c --- /dev/null +++ b/python/ql/test/query-tests/Classes/multiple/SuperclassInitCalledMultipleTimes.qlref @@ -0,0 +1 @@ +Classes/SuperclassInitCalledMultipleTimes.ql diff --git a/python/ql/test/query-tests/Classes/multiple/multiple_del.py b/python/ql/test/query-tests/Classes/multiple/multiple_del.py new file mode 100644 index 000000000000..284f6bf6969f --- /dev/null +++ b/python/ql/test/query-tests/Classes/multiple/multiple_del.py @@ -0,0 +1,38 @@ +#Calling a method multiple times by using explicit calls when a base uses super() +class Base(object): + + def __del__(self): + pass + +class Y1(Base): + + def __del__(self): + super(Y1, self).__del__() + +class Y2(Base): + + def __del__(self): + super(Y2, self).__del__() #When `type(self) == Y3` this calls `Y1.__del__` + +class Y3(Y2, Y1): + + def __del__(self): + Y1.__del__(self) + Y2.__del__(self) + +#Calling a method multiple times by using explicit calls when a base inherits from other base +class Z1(object): + + def __del__(self): + pass + +class Z2(Z1): + + def __del__(self): + Z1.__del__(self) + +class Z3(Z2, Z1): + + def __del__(self): + Z1.__del__(self) + Z2.__del__(self) diff --git a/python/ql/test/query-tests/Classes/multiple/multiple_init.py b/python/ql/test/query-tests/Classes/multiple/multiple_init.py new file mode 100644 index 000000000000..6a97ef67f6ca --- /dev/null +++ b/python/ql/test/query-tests/Classes/multiple/multiple_init.py @@ -0,0 +1,76 @@ +#Calling a method multiple times by using explicit calls when a base uses super() +class Base(object): + + def __init__(self): + pass + +class C1(Base): + + def __init__(self): + super(C1, self).__init__() + +class C2(Base): + + def __init__(self): + super(C2, self).__init__() #When `type(self) == C3` this calls `C1.__init__` + +class C3(C2, C1): + + def __init__(self): + C1.__init__(self) + C2.__init__(self) + +#Calling a method multiple times by using explicit calls when a base inherits from other base +class D1(object): + + def __init__(self): + pass + +class D2(D1): + + def __init__(self): + D1.__init__(self) + +class D3(D2, D1): + + def __init__(self): + D1.__init__(self) + D2.__init__(self) + +#OK to call object.__init__ multiple times +class E1(object): + + def __init__(self): + super(E1, self).__init__() + +class E2(object): + + def __init__(self): + object.__init__(self) + +class E3(E2, E1): + + def __init__(self): + E1.__init__(self) + E2.__init__(self) + +#Two invocations, but can only be called once +class F1(Base): + + def __init__(self, cond): + if cond: + Base.__init__(self) + else: + Base.__init__(self) + +#Single call, splitting causes what seems to be multiple invocations. +class F2(Base): + + def __init__(self, cond): + if cond: + pass + if cond: + pass + Base.__init__(self) + + diff --git a/python/ql/test/query-tests/Classes/overwriting-attribute/OverwritingAttributeInSuperClass.expected b/python/ql/test/query-tests/Classes/overwriting-attribute/OverwritingAttributeInSuperClass.expected new file mode 100644 index 000000000000..a396d393db16 --- /dev/null +++ b/python/ql/test/query-tests/Classes/overwriting-attribute/OverwritingAttributeInSuperClass.expected @@ -0,0 +1,2 @@ +| overwriting_attribute.py:5:9:5:20 | AssignStmt | Assignment overwrites attribute var, which was previously defined in subclass $@. | overwriting_attribute.py:10:9:10:20 | AssignStmt | D | +| overwriting_attribute.py:23:9:23:20 | AssignStmt | Assignment overwrites attribute var, which was previously defined in superclass $@. | overwriting_attribute.py:17:9:17:20 | AssignStmt | E | diff --git a/python/ql/test/query-tests/Classes/overwriting-attribute/OverwritingAttributeInSuperClass.qlref b/python/ql/test/query-tests/Classes/overwriting-attribute/OverwritingAttributeInSuperClass.qlref new file mode 100644 index 000000000000..b29c4d250250 --- /dev/null +++ b/python/ql/test/query-tests/Classes/overwriting-attribute/OverwritingAttributeInSuperClass.qlref @@ -0,0 +1 @@ +Classes/OverwritingAttributeInSuperClass.ql diff --git a/python/ql/test/query-tests/Classes/overwriting-attribute/overwriting_attribute.py b/python/ql/test/query-tests/Classes/overwriting-attribute/overwriting_attribute.py new file mode 100644 index 000000000000..0372db0b2159 --- /dev/null +++ b/python/ql/test/query-tests/Classes/overwriting-attribute/overwriting_attribute.py @@ -0,0 +1,23 @@ +#Attribute set in both superclass and subclass +class C(object): + + def __init__(self): + self.var = 0 + +class D(C): + + def __init__(self): + self.var = 1 # self.var will be overwritten + C.__init__(self) + +#Attribute set in both superclass and subclass +class E(object): + + def __init__(self): + self.var = 0 # self.var will be overwritten + +class F(E): + + def __init__(self): + E.__init__(self) + self.var = 1 diff --git a/python/ql/test/query-tests/Classes/should-be-context-manager/ShouldBeContextManager.expected b/python/ql/test/query-tests/Classes/should-be-context-manager/ShouldBeContextManager.expected new file mode 100644 index 000000000000..47c773804ae8 --- /dev/null +++ b/python/ql/test/query-tests/Classes/should-be-context-manager/ShouldBeContextManager.expected @@ -0,0 +1,2 @@ +| should_be_context_manager.py:3:1:3:22 | class MegaDel | Class MegaDel implements __del__ (presumably to release some resource). Consider making it a context manager. | +| should_be_context_manager.py:16:1:16:22 | class MiniDel | Class MiniDel implements __del__ (presumably to release some resource). Consider making it a context manager. | diff --git a/python/ql/test/query-tests/Classes/should-be-context-manager/ShouldBeContextManager.qlref b/python/ql/test/query-tests/Classes/should-be-context-manager/ShouldBeContextManager.qlref new file mode 100644 index 000000000000..f555b0af07a3 --- /dev/null +++ b/python/ql/test/query-tests/Classes/should-be-context-manager/ShouldBeContextManager.qlref @@ -0,0 +1 @@ +Classes/ShouldBeContextManager.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/should-be-context-manager/should_be_context_manager.py b/python/ql/test/query-tests/Classes/should-be-context-manager/should_be_context_manager.py new file mode 100644 index 000000000000..68fc81206a37 --- /dev/null +++ b/python/ql/test/query-tests/Classes/should-be-context-manager/should_be_context_manager.py @@ -0,0 +1,22 @@ +#Should be context manager + +class MegaDel(object): + + def __del__(self): + a = self.x + self.y + if a: + print(a) + if sys._getframe().f_lineno > 100: + print("Hello") + sum = 0 + for a in range(100): + sum += a + print(sum) + +class MiniDel(object): + + def close(self): + pass + + def __del__(self): + self.close() \ No newline at end of file diff --git a/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.expected b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.expected new file mode 100644 index 000000000000..ae922ef52646 --- /dev/null +++ b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.expected @@ -0,0 +1 @@ +| subclass_shadowing.py:10:5:10:21 | FunctionExpr | Method shadow is shadowed by $@ in super class 'Base'. | subclass_shadowing.py:6:9:6:23 | AssignStmt | an attribute | diff --git a/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.qlref b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.qlref new file mode 100644 index 000000000000..5fed3f9f8fc6 --- /dev/null +++ b/python/ql/test/query-tests/Classes/subclass-shadowing/SubclassShadowing.qlref @@ -0,0 +1 @@ +Classes/SubclassShadowing.ql diff --git a/python/ql/test/query-tests/Classes/subclass-shadowing/subclass_shadowing.py b/python/ql/test/query-tests/Classes/subclass-shadowing/subclass_shadowing.py new file mode 100644 index 000000000000..98e7f992e84e --- /dev/null +++ b/python/ql/test/query-tests/Classes/subclass-shadowing/subclass_shadowing.py @@ -0,0 +1,30 @@ +#Subclass shadowing + +class Base(object): + + def __init__(self): + self.shadow = 4 + +class Derived(Base): + + def shadow(self): + pass + + +#OK if the super class defines the method as well. +#Since the original method must exist for some reason. +#See JSONEncoder.default for real example + +class Base2(object): + + def __init__(self, shadowy=None): + if shadowy: + self.shadow = shadowy + + def shadow(self): + pass + +class Derived2(Base2): + + def shadow(self): + return 0 diff --git a/python/ql/test/query-tests/Classes/undefined-attribute/MaybeUndefinedClassAttribute.expected b/python/ql/test/query-tests/Classes/undefined-attribute/MaybeUndefinedClassAttribute.expected new file mode 100644 index 000000000000..1aebf13f2bce --- /dev/null +++ b/python/ql/test/query-tests/Classes/undefined-attribute/MaybeUndefinedClassAttribute.expected @@ -0,0 +1,4 @@ +| undefined_attribute.py:27:16:27:29 | Attribute | Attribute 'may_exist' is not defined in the class body nor in the __init__() method, but it is defined $@ | undefined_attribute.py:11:9:11:22 | Attribute | here | +| undefined_attribute.py:184:16:184:32 | Attribute | Attribute 'return_queue' is not defined in the class body nor in the __init__() method, but it is defined $@ | undefined_attribute.py:181:13:181:29 | Attribute | here | +| undefined_attribute.py:257:16:257:31 | Attribute | Attribute 'glance_host' is not defined in the class body nor in the __init__() method, but it is defined $@ | undefined_attribute.py:262:13:262:28 | Attribute | here | +| undefined_attribute.py:258:16:258:31 | Attribute | Attribute 'glance_port' is not defined in the class body nor in the __init__() method, but it is defined $@ | undefined_attribute.py:263:10:263:25 | Attribute | here | diff --git a/python/ql/test/query-tests/Classes/undefined-attribute/MaybeUndefinedClassAttribute.qlref b/python/ql/test/query-tests/Classes/undefined-attribute/MaybeUndefinedClassAttribute.qlref new file mode 100644 index 000000000000..d4986ffc84ce --- /dev/null +++ b/python/ql/test/query-tests/Classes/undefined-attribute/MaybeUndefinedClassAttribute.qlref @@ -0,0 +1 @@ +Classes/MaybeUndefinedClassAttribute.ql diff --git a/python/ql/test/query-tests/Classes/undefined-attribute/UndefinedClassAttribute.expected b/python/ql/test/query-tests/Classes/undefined-attribute/UndefinedClassAttribute.expected new file mode 100644 index 000000000000..deb82710cf54 --- /dev/null +++ b/python/ql/test/query-tests/Classes/undefined-attribute/UndefinedClassAttribute.expected @@ -0,0 +1,4 @@ +| undefined_attribute.py:24:16:24:30 | Attribute | Attribute 'not_exists' is not defined in either the class body or in any method | +| undefined_attribute.py:109:16:109:21 | Attribute | Attribute 'y' is not defined in either the class body or in any method | +| undefined_attribute.py:250:16:250:31 | Attribute | Attribute 'glance_host' is not defined in either the class body or in any method | +| undefined_attribute.py:251:16:251:31 | Attribute | Attribute 'glance_port' is not defined in either the class body or in any method | diff --git a/python/ql/test/query-tests/Classes/undefined-attribute/UndefinedClassAttribute.qlref b/python/ql/test/query-tests/Classes/undefined-attribute/UndefinedClassAttribute.qlref new file mode 100644 index 000000000000..7ac0a3b18b77 --- /dev/null +++ b/python/ql/test/query-tests/Classes/undefined-attribute/UndefinedClassAttribute.qlref @@ -0,0 +1 @@ +Classes/UndefinedClassAttribute.ql diff --git a/python/ql/test/query-tests/Classes/undefined-attribute/undefined_attribute.py b/python/ql/test/query-tests/Classes/undefined-attribute/undefined_attribute.py new file mode 100644 index 000000000000..0eec9974ef15 --- /dev/null +++ b/python/ql/test/query-tests/Classes/undefined-attribute/undefined_attribute.py @@ -0,0 +1,263 @@ +#Non existent class attribute + +class Attributes(object): + + exists1 = 1 + + def __init__(self): + self.exists2 = 2 + + def method(self): + self.may_exist = 3 + + def ok1(self): + print (self.exists1) + + def ok2(self): + print (self.exists2) + + def ok3(self): + self.local_exists = 4 + print (self.local_exists) + + def neca1(self): + print (self.not_exists) + + def neca2(self): + print (self.may_exist) + +#This is OK +class SetViaDict(object): + + def __init__(self, x): + self.__dict__['x'] = x + + def use_x(self): + return self.x + + +#This is also OK +class SetLocally(object): + + def use_x(self): + self.x = 1 + return self.x + + +class UsesSetattr(object): + + def __init__(self, vals): + for k, v in vals.items(): + setattr(self, k, v) + + def use_values(self): + return self.x, self.y, self.z + +#OK +class GuardedByHasAttr(object): + + def ok4(self): + if hasattr(self, "x"): + return self.x + else: + return None + +class HasGetAttr(object): + + def __getattr__(self, name): + return name + + def use_values(self): + return self.x, self.y, self.z + +class HasGetAttribute(object): + + def __getattribute__(self, name): + return name + + def use_values(self): + return self.x, self.y, self.z + + + + + + + + + + + + + + + + +class DecoratedInit(object): + + @decorator + def __init__(self): + self.x = x + + def use(self): + return self.x + +#This is not OK +class NoInit(object): + + def use_y(self): + return self.y + +#This is also OK +class SetLocally2(object): + + def __init__(self): + pass + + def use_y(self): + self.x = 0 + self.y = 1 + if cond: + print(self.y) + else: + return False + return self.y + +#Guarded +class Guarded(object): + + def __init__(self): + self.guard = False + + def set_x(self): + self.guard = True + self.x = 1 + + def use_x(self): + if self.guard: + return self.x + else: + return 0 + +#ODASA-2034 +class ODASA2034A(object): + + def __init__(self, data): + d = self.__dict__ + d['data'] = data + + def use_data(self): + return self.data + +class ODASA2034B(object): + + def __init__(self, key, value): + d = self.__dict__ + d[key] = value + + def use_data(self): + return self.data + + +class Test5(object): + + def readFromStream(stream): + word = stream.read(4) + if word == "true": + return BooleanObject(True) + elif word == "fals": + stream.read(1) + return BooleanObject(False) + assert False + readFromStream = staticmethod(readFromStream) + + +class Test1(object): + + def __init__(self, application): + self.rabbitmq_channel = None + + def queue_declared(frame): # called in callback + self.return_queue = frame.method.queue + + def use_it(self): + return self.return_queue + + +#Check for FPs when overriding builtin methods + +class PrintingDict(dict): + + def pop(self): + print ("pop") + return dict.pop(self) + + def __setitem__(self, key, value): + print("__setitem__") + return dict.__setitem__(self, key, value) + +#Locally set by Call +#ODASA-4612 +class WSGIContext(object): + + def _app_call(self, env): + self._response_headers = None + + +class Odasa4612(WSGIContext): + + def meth1(self, arg): + val = self._app_call(arg) + if self._response_headers is None: + self._response_headers = [] + + + +class OK9(object): + cls_attr = 0 + def __init__(self): + self.attr = self.cls_attr + + + +class Foo1(object): + pass + +foo = Foo1() +setattr(foo, 'a', 1) +assert foo.a == 1 + +class Foo2(object): + pass + +setattr(Foo2, 'a', 1) +assert Foo2.a == 1 + +# False positive observed at customer + +class Customer1(object): + + def x(self): + if not hasattr(self, "attr"): + return None + else: + return self.attr + +#ODASA-4619 +class Odasa4619a(object): + + def call(self): + host = self.glance_host + port = self.glance_port + + +class Odasa4619b(object): + + def call(self): + host = self.glance_host + port = self.glance_port + + @decorator + def foo(self): + (x, self.glance_host, + self.glance_port, y) = bar() diff --git a/python/ql/test/query-tests/Classes/useless/UselessClass.expected b/python/ql/test/query-tests/Classes/useless/UselessClass.expected new file mode 100644 index 000000000000..f4f62cd80c7f --- /dev/null +++ b/python/ql/test/query-tests/Classes/useless/UselessClass.expected @@ -0,0 +1,2 @@ +| test.py:28:1:28:23 | Class Useless1 | Class Useless1 defines only one public method, which should be replaced by a function. | +| test.py:37:1:37:23 | Class Useless2 | Class Useless2 defines only one public method, which should be replaced by a function. | diff --git a/python/ql/test/query-tests/Classes/useless/UselessClass.qlref b/python/ql/test/query-tests/Classes/useless/UselessClass.qlref new file mode 100644 index 000000000000..9c8e87e962cd --- /dev/null +++ b/python/ql/test/query-tests/Classes/useless/UselessClass.qlref @@ -0,0 +1 @@ +Classes/UselessClass.ql diff --git a/python/ql/test/query-tests/Classes/useless/test.py b/python/ql/test/query-tests/Classes/useless/test.py new file mode 100644 index 000000000000..40c9e56e117e --- /dev/null +++ b/python/ql/test/query-tests/Classes/useless/test.py @@ -0,0 +1,65 @@ + +#Useless class + +class Useful1(object): + + def __init__(self): + pass + + def do_something(self): + pass + + def do_something_else(self): + pass + + def do_yet_another_thing(self): + pass + + +class Useful2(object): + + def do_something(self): + pass + + def do_something_else(self): + pass + + +class Useless1(object): + + def __init__(self): + pass + + def do_something(self): + pass + + +class Useless2(object): + + def do_something(self): + pass + +class Stateful1(object): + + def __init__(self): + self.data = [] + + def do_something(self, x): + self.data.append(x) + + +class Stateful2(object): + + def __init__(self): + self.data = None + + def do_something(self, x): + self.data = x + +class Inherited(object): + pass + +class Inherits(Inherited): + + def do_something(self): + pass diff --git a/python/ql/test/query-tests/Exceptions/general/CatchingBaseException.expected b/python/ql/test/query-tests/Exceptions/general/CatchingBaseException.expected new file mode 100644 index 000000000000..8cbb6d9c961c --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/CatchingBaseException.expected @@ -0,0 +1,2 @@ +| exceptions_test.py:7:5:7:11 | ExceptStmt | Except block directly handles BaseException. | +| exceptions_test.py:71:5:71:25 | ExceptStmt | Except block directly handles BaseException. | diff --git a/python/ql/test/query-tests/Exceptions/general/CatchingBaseException.qlref b/python/ql/test/query-tests/Exceptions/general/CatchingBaseException.qlref new file mode 100644 index 000000000000..5588dbf2c7b4 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/CatchingBaseException.qlref @@ -0,0 +1 @@ +Exceptions/CatchingBaseException.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Exceptions/general/EmptyExcept.expected b/python/ql/test/query-tests/Exceptions/general/EmptyExcept.expected new file mode 100755 index 000000000000..57c50449af8e --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/EmptyExcept.expected @@ -0,0 +1,2 @@ +| exceptions_test.py:7:5:7:11 | ExceptStmt | 'except' clause does nothing but pass and there is no explanatory comment. | +| exceptions_test.py:13:5:13:21 | ExceptStmt | 'except' clause does nothing but pass and there is no explanatory comment. | diff --git a/python/ql/test/query-tests/Exceptions/general/EmptyExcept.qlref b/python/ql/test/query-tests/Exceptions/general/EmptyExcept.qlref new file mode 100644 index 000000000000..3f4987046b12 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/EmptyExcept.qlref @@ -0,0 +1 @@ +Exceptions/EmptyExcept.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Exceptions/general/IllegalExceptionHandlerType.expected b/python/ql/test/query-tests/Exceptions/general/IllegalExceptionHandlerType.expected new file mode 100644 index 000000000000..0864dd0fe308 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/IllegalExceptionHandlerType.expected @@ -0,0 +1,4 @@ +| exceptions_test.py:51:5:51:25 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | exceptions_test.py:33:1:33:28 | ControlFlowNode for ClassExpr | class 'NotException1' | +| exceptions_test.py:54:5:54:25 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | exceptions_test.py:36:1:36:28 | ControlFlowNode for ClassExpr | class 'NotException2' | +| exceptions_test.py:112:5:112:22 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | exceptions_test.py:107:12:107:14 | ControlFlowNode for FloatLiteral | instance of 'float' | +| pypy_test.py:14:5:14:14 | ExceptStmt | Non-exception $@ in exception handler which will never match raised exception. | pypy_test.py:14:12:14:13 | ControlFlowNode for IntegerLiteral | instance of 'int' | diff --git a/python/ql/test/query-tests/Exceptions/general/IllegalExceptionHandlerType.qlref b/python/ql/test/query-tests/Exceptions/general/IllegalExceptionHandlerType.qlref new file mode 100644 index 000000000000..6d49710a7599 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/IllegalExceptionHandlerType.qlref @@ -0,0 +1 @@ +Exceptions/IllegalExceptionHandlerType.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Exceptions/general/IllegalRaise.expected b/python/ql/test/query-tests/Exceptions/general/IllegalRaise.expected new file mode 100644 index 000000000000..2625bb14a96b --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/IllegalRaise.expected @@ -0,0 +1,3 @@ +| exceptions_test.py:40:5:40:23 | Raise | Illegal class 'NotException1' raised; will result in a TypeError being raised instead. | +| exceptions_test.py:43:5:43:21 | Raise | Illegal class 'str' raised; will result in a TypeError being raised instead. | +| exceptions_test.py:46:5:46:25 | Raise | Illegal class 'NotException2' raised; will result in a TypeError being raised instead. | diff --git a/python/ql/test/query-tests/Exceptions/general/IllegalRaise.qlref b/python/ql/test/query-tests/Exceptions/general/IllegalRaise.qlref new file mode 100644 index 000000000000..5a515d5656d3 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/IllegalRaise.qlref @@ -0,0 +1 @@ +Exceptions/IllegalRaise.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Exceptions/general/IncorrectExceptOrder.expected b/python/ql/test/query-tests/Exceptions/general/IncorrectExceptOrder.expected new file mode 100644 index 000000000000..21494493acbf --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/IncorrectExceptOrder.expected @@ -0,0 +1 @@ +| exceptions_test.py:64:1:64:22 | ExceptStmt | Except block for $@ is unreachable; the more general $@ for $@ will always be executed in preference. | file://:Compiled Code:0:0:0:0 | builtin-class AttributeError | AttributeError | exceptions_test.py:62:1:62:17 | ExceptStmt | except block | file://:Compiled Code:0:0:0:0 | builtin-class Exception | Exception | diff --git a/python/ql/test/query-tests/Exceptions/general/IncorrectExceptOrder.qlref b/python/ql/test/query-tests/Exceptions/general/IncorrectExceptOrder.qlref new file mode 100644 index 000000000000..bc4c3a070813 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/IncorrectExceptOrder.qlref @@ -0,0 +1 @@ +Exceptions/IncorrectExceptOrder.ql diff --git a/python/ql/test/query-tests/Exceptions/general/NotImplementedIsNotAnException.expected b/python/ql/test/query-tests/Exceptions/general/NotImplementedIsNotAnException.expected new file mode 100644 index 000000000000..c9fa73e7c127 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/NotImplementedIsNotAnException.expected @@ -0,0 +1,2 @@ +| exceptions_test.py:170:11:170:24 | NotImplemented | NotImplemented is not an Exception. Did you mean NotImplementedError? | +| exceptions_test.py:173:11:173:24 | NotImplemented | NotImplemented is not an Exception. Did you mean NotImplementedError? | diff --git a/python/ql/test/query-tests/Exceptions/general/NotImplementedIsNotAnException.qlref b/python/ql/test/query-tests/Exceptions/general/NotImplementedIsNotAnException.qlref new file mode 100644 index 000000000000..61ac527ffb99 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/NotImplementedIsNotAnException.qlref @@ -0,0 +1 @@ +Exceptions/NotImplementedIsNotAnException.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Exceptions/general/exceptions_test.py b/python/ql/test/query-tests/Exceptions/general/exceptions_test.py new file mode 100644 index 000000000000..d3f782f874f7 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/exceptions_test.py @@ -0,0 +1,173 @@ + +#Empty Except + +def ee1(val): + try: + val.attr + except: + pass + +def ee1(val): + try: + val.attr() + except TypeError: + pass + +def ee2(val): + try: + val.attr + except Error: + #But it is OK if there is a comment + pass + +#OK with an else clause as well... + +def ee3(val): + try: + val.attr + except Error: + pass + else: + return 42 + +class NotException1(object): + pass + +class NotException2(object): + pass + +def illegal_raise_type(): + raise NotException1 + +def illegal_raise_value1(): + raise "Exception" + +def illegal_raise_value2(): + raise NotException2() + +def illegal_handler(): + try: + illegal_raise() + except NotException1: + #Must do something + print("NotException1") + except NotException2: + #Must do something + print("NotException2") + + +#Incorrect except order +try: + val.attr +except Exception: + print (2) +except AttributeError: + print (3) + +#Catch BaseException +def catch_base_exception(): + try: + illegal_raise() + except BaseException: + #Consumes KeyboardInterrupt + pass + +def catch_base_exception_ok(): + try: + illegal_raise() + except BaseException: + raise + +def legal_handler1(): + try: + illegal_raise() + except (IOError, KeyError): + print ("Caught exception") + +pair = IOError, KeyError +triple = pair, AttributeError + +def legal_handler2(): + try: + illegal_raise() + except pair: + print ("Caught exception") + try: + illegal_raise() + except triple: + print ("Caught exception") + +def legal_handler3(): + try: + illegal_raise() + except could_be_anything(): + print ("Caught exception") + +def a_number(): + return 4.0 + +def illegal_handler2(): + try: + illegal_raise() + except a_number(): + print ("Caught exception") + +def stop_iter_ok(seq): + try: + next(seq) + except StopIteration: + pass + +#Guarded None in nested function +def f(x=None): + def inner(arg): + if x: + raise x + +#ODASA-4705 +def g(cond): + try: + if cond: + return may_raise_io_error() + else: + raise KeyError + except IOError: + pass # This is OK, as it is just passing to the following statement which handles the exception. + return 0 + +def ee4(x): + try: + del x.attr + except AttributeError: + pass + +def ee5(x): + try: + x[0] + except IndexError: + pass + +def ee6(x): + try: + del x[0] + except IndexError: + pass + +def ee7(x): + try: + delattr(x, "attr") + except AttributeError: + pass + +def ee8(x): + try: + x.encode("martian-18") + except UnicodeEncodeError: + pass + + #These are so common, we give warnings not errors. +def foo(): + raise NotImplemented + +def bar(): + raise NotImplemented() diff --git a/python/ql/test/query-tests/Exceptions/general/pypy_test.py b/python/ql/test/query-tests/Exceptions/general/pypy_test.py new file mode 100644 index 000000000000..857e78d6d942 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/general/pypy_test.py @@ -0,0 +1,20 @@ + +def test(): + class A(BaseException): + class __metaclass__(type): + def __getattribute__(self, name): + if flag and name == '__bases__': + fail("someone read bases attr") + else: + return type.__getattribute__(self, name) + + try: + a = A() + raise a + except 42: + #Some comment + pass + except A: + #Another comment + pass + diff --git a/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.expected b/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.expected new file mode 100644 index 000000000000..289b8fb5a0d9 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.expected @@ -0,0 +1,2 @@ +| test.py:5:15:5:22 | ControlFlowNode for next() | Call to next() in a generator | +| test.py:10:20:10:27 | ControlFlowNode for next() | Call to next() in a generator | diff --git a/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.qlref b/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.qlref new file mode 100644 index 000000000000..7fe5d609705b --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.qlref @@ -0,0 +1 @@ +Exceptions/UnguardedNextInGenerator.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Exceptions/generators/test.py b/python/ql/test/query-tests/Exceptions/generators/test.py new file mode 100644 index 000000000000..e8fe159a5591 --- /dev/null +++ b/python/ql/test/query-tests/Exceptions/generators/test.py @@ -0,0 +1,49 @@ +#Unguarded calls to next() + +def bad1(it): + while True: + yield next(it) + +def bad2(seq): + it = iter(seq) + #Not OK as seq may be empty + raise KeyError(next(it)) + yield 0 + +def ok1(seq): + #Not a generator + it = iter(seq) + #Not OK as seq may be empty + raise KeyError(next(it)) + +def ok2(seq): + if seq: + it = iter(seq) + #OK seq is non-empty so next(it) will not raise StopIteration + raise KeyError(next(it)) + yield 0 + +def explicit_raise_stop_iter(seq): + for i in seq: + yield seq + raise StopIteration() + +def ok3(seq): + it = iter(seq) + try: + yield next(iter) + except StopIteration: + return + +def ok4(seq, ctx): + try: + with ctx: + yield next(iter) + except StopIteration: + return + +#ODASA-6536 +def next_in_comp(seq, fields): + seq_iter = iter(seq) + values = [ next(seq_iter) if f.attname in NAMES else DEFAULT for f in fields ] + return values diff --git a/python/ql/test/query-tests/Expressions/Arguments/WrongNameForArgumentInCall.expected b/python/ql/test/query-tests/Expressions/Arguments/WrongNameForArgumentInCall.expected new file mode 100644 index 000000000000..dc88cf8496e4 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Arguments/WrongNameForArgumentInCall.expected @@ -0,0 +1,5 @@ +| wrong_arguments.py:57:1:57:7 | f0() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:3:1:3:10 | Function f0 | function f0 | +| wrong_arguments.py:58:1:58:7 | f1() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:6:1:6:20 | Function f1 | function f1 | +| wrong_arguments.py:59:1:59:12 | f2() | Keyword argument 'y' is not a supported parameter name of $@. | wrong_arguments.py:9:1:9:14 | Function f2 | function f2 | +| wrong_arguments.py:103:1:103:27 | f6() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 | +| wrong_arguments.py:115:1:115:13 | f1() | Keyword argument 'z' is not a supported parameter name of $@. | wrong_arguments.py:6:1:6:20 | Function f1 | function f1 | diff --git a/python/ql/test/query-tests/Expressions/Arguments/WrongNameForArgumentInCall.qlref b/python/ql/test/query-tests/Expressions/Arguments/WrongNameForArgumentInCall.qlref new file mode 100644 index 000000000000..3599f204f55e --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Arguments/WrongNameForArgumentInCall.qlref @@ -0,0 +1 @@ +Expressions/WrongNameForArgumentInCall.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/Arguments/WrongNumberArgumentsInCall.expected b/python/ql/test/query-tests/Expressions/Arguments/WrongNumberArgumentsInCall.expected new file mode 100644 index 000000000000..c23418acd454 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Arguments/WrongNumberArgumentsInCall.expected @@ -0,0 +1,25 @@ +| use_mox.py:28:1:28:4 | f0() | Call to $@ with too few arguments; should be no fewer than 1. | use_mox.py:7:1:7:10 | Function f0 | function f0 | +| use_mox.py:29:1:29:5 | f1() | Call to $@ with too few arguments; should be no fewer than 2. | use_mox.py:10:1:10:13 | Function f1 | function f1 | +| use_mox.py:32:1:32:8 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | use_mox.py:15:5:15:20 | Function m0 | method C.m0 | +| use_mox.py:33:1:33:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | use_mox.py:18:5:18:23 | Function m1 | method C.m1 | +| wrong_arguments.py:29:1:29:4 | f0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:3:1:3:10 | Function f0 | function f0 | +| wrong_arguments.py:30:1:30:4 | f1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:6:1:6:20 | Function f1 | function f1 | +| wrong_arguments.py:31:1:31:4 | f2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:9:1:9:14 | Function f2 | function f2 | +| wrong_arguments.py:32:1:32:4 | f3() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:12:1:12:24 | Function f3 | function f3 | +| wrong_arguments.py:33:1:33:4 | f4() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:15:1:15:15 | Function f4 | function f4 | +| wrong_arguments.py:34:1:34:4 | f5() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:18:1:18:25 | Function f5 | function f5 | +| wrong_arguments.py:35:1:35:5 | f6() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 | +| wrong_arguments.py:36:1:36:7 | f7() | Call to $@ with too few arguments; should be no fewer than 3. | wrong_arguments.py:24:1:24:16 | Function f7 | function f7 | +| wrong_arguments.py:40:1:40:7 | f0() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:3:1:3:10 | Function f0 | function f0 | +| wrong_arguments.py:41:1:41:9 | f1() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:6:1:6:20 | Function f1 | function f1 | +| wrong_arguments.py:42:1:42:9 | f5() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:18:1:18:25 | Function f5 | function f5 | +| wrong_arguments.py:43:1:43:9 | f6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 | +| wrong_arguments.py:44:1:44:11 | f6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 | +| wrong_arguments.py:81:1:81:5 | l0() | Call to $@ with too many arguments; should be no more than 0. | wrong_arguments.py:70:6:70:15 | Function lambda | function lambda | +| wrong_arguments.py:82:1:82:7 | l1() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:71:6:71:21 | Function lambda | function lambda | +| wrong_arguments.py:83:1:83:8 | l1d() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:72:7:72:25 | Function lambda | function lambda | +| wrong_arguments.py:86:1:86:4 | l1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:71:6:71:21 | Function lambda | function lambda | +| wrong_arguments.py:96:1:96:12 | f6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 | +| wrong_arguments.py:97:1:97:7 | f6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 | +| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:126:5:126:31 | Function spam | method Eggs2.spam | +| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | wrong_arguments.py:121:5:121:19 | Function spam | method Eggs1.spam | diff --git a/python/ql/test/query-tests/Expressions/Arguments/WrongNumberArgumentsInCall.qlref b/python/ql/test/query-tests/Expressions/Arguments/WrongNumberArgumentsInCall.qlref new file mode 100644 index 000000000000..1bffe8f1cad4 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Arguments/WrongNumberArgumentsInCall.qlref @@ -0,0 +1 @@ +Expressions/WrongNumberArgumentsInCall.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/Arguments/mox.py b/python/ql/test/query-tests/Expressions/Arguments/mox.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Arguments/mox.py @@ -0,0 +1 @@ + diff --git a/python/ql/test/query-tests/Expressions/Arguments/use_mox.py b/python/ql/test/query-tests/Expressions/Arguments/use_mox.py new file mode 100644 index 000000000000..35d35574895a --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Arguments/use_mox.py @@ -0,0 +1,33 @@ + +import mox + +#Use it +mox + +def f0(x): + pass + +def f1(x, y): + pass + +class C(object): + + def m0(self, x): + pass + + def m1(self, x, y): + pass + +# These are treated as magically OK since we are using mox + +C.m0(1) +C.m1(1,2) + +#But normal functions are treated normally + +f0() +f1(1) + +#As are normal methods +C().m0() +C().m1(1) diff --git a/python/ql/test/query-tests/Expressions/Arguments/wrong_arguments.py b/python/ql/test/query-tests/Expressions/Arguments/wrong_arguments.py new file mode 100644 index 000000000000..284d1d19bc36 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Arguments/wrong_arguments.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +def f0(x): + pass + +def f1(x, y = None): + pass + +def f2(x, *y): + pass + +def f3(x, y = None, *z): + pass + +def f4(x, **y): + pass + +def f5(x, y = None, **z): + pass + +def f6(x, y): + pass + +def f7(x, y, z): + pass + +# Too few arguments + +f0() +f1() +f2() +f3() +f4() +f5() +f6(1) +f7(1,2) + +#Too many arguments + +f0(1,2) +f1(1,2,3) +f5(1,2,3) +f6(1,2,3) +f6(1,2,3,4) + +#OK + +#Not too few +f7(*t) + +#Not too many + +f2(1,2,3,4,5,6) + + +#Illegal name +f0(y=1) +f1(z=1) +f2(x=0, y=1) + + +#Ok name +f0(x=0) +f1(x=0, y=1) +f4(q=4) + +#This is correct, but a bit weird. +f6(**{'x':1, 'y':2}) + +l0 = lambda : 0 +l1 = lambda x : 2 * x +l1d = lambda x = 0 : 2 *x + +#OK +l0() +l1(1) +l1d() +l1d(1) + +#Too many +l0(1) +l1(1,2) +l1d(1,2) + +#Too few +l1() + + +t2 = (1,2) +t3 = (1,2,3) + +#Ok +f(*t2) + +#Too many +f6(*(1,2,3)) +f6(*t3) + +#Ok +f6(**{'x':1, 'y':2}) + +#Illegal name +f6(**{'x':1, 'y':2, 'z':3}) + +#Theoretically -1 arguments required. Don't report +class C(object): + + def f(): + pass + +C().f() + + +#Too many and wrong name -- check only wrong name is flagged. +f1(x, y, z=1) + + +#Overriding and call is wrong. +class Eggs1(object): + + def spam(self): + pass + +class Eggs2(Eggs1): + + def spam(self, arg0, arg1): + pass + +e = Eggs1() if cond else Eggs2() +e.spam(0) + diff --git a/python/ql/test/query-tests/Expressions/Formatting/MixedExplicitImplicitIn3101Format.expected b/python/ql/test/query-tests/Expressions/Formatting/MixedExplicitImplicitIn3101Format.expected new file mode 100644 index 000000000000..d055082fe46b --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Formatting/MixedExplicitImplicitIn3101Format.expected @@ -0,0 +1,2 @@ +| test.py:3:17:3:23 | Str | Formatting string mixes implicitly and explicitly numbered fields. | +| test.py:8:17:8:23 | Str | Formatting string mixes implicitly and explicitly numbered fields. | diff --git a/python/ql/test/query-tests/Expressions/Formatting/MixedExplicitImplicitIn3101Format.qlref b/python/ql/test/query-tests/Expressions/Formatting/MixedExplicitImplicitIn3101Format.qlref new file mode 100644 index 000000000000..3b9a8dc0ccf9 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Formatting/MixedExplicitImplicitIn3101Format.qlref @@ -0,0 +1 @@ +Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/Formatting/UnusedArgumentIn3101Format.expected b/python/ql/test/query-tests/Expressions/Formatting/UnusedArgumentIn3101Format.expected new file mode 100644 index 000000000000..5dc11d99643c --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Formatting/UnusedArgumentIn3101Format.expected @@ -0,0 +1,4 @@ +| test.py:29:1:29:50 | Attribute() | Too many arguments for string format. Format $@ requires only 2, but 3 are provided. | test.py:5:20:5:29 | Str | "{0}, {1}" | +| test.py:30:1:30:51 | format() | Too many arguments for string format. Format $@ requires only 2, but 3 are provided. | test.py:10:20:10:29 | Str | "{0}, {1}" | +| test.py:32:1:32:50 | Attribute() | Too many arguments for string format. Format $@ requires only 2, but 3 are provided. | test.py:6:20:6:27 | Str | "{}, {}" | +| test.py:33:1:33:51 | format() | Too many arguments for string format. Format $@ requires only 2, but 3 are provided. | test.py:11:20:11:27 | Str | "{}, {}" | diff --git a/python/ql/test/query-tests/Expressions/Formatting/UnusedArgumentIn3101Format.qlref b/python/ql/test/query-tests/Expressions/Formatting/UnusedArgumentIn3101Format.qlref new file mode 100644 index 000000000000..b3e654ad0526 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Formatting/UnusedArgumentIn3101Format.qlref @@ -0,0 +1 @@ +Expressions/Formatting/UnusedArgumentIn3101Format.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/Formatting/UnusedNamedArgumentIn3101Format.expected b/python/ql/test/query-tests/Expressions/Formatting/UnusedNamedArgumentIn3101Format.expected new file mode 100644 index 000000000000..849920f8c070 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Formatting/UnusedNamedArgumentIn3101Format.expected @@ -0,0 +1,8 @@ +| test.py:17:1:17:44 | Attribute() | Surplus named argument for string format. An argument named 'world' is provided, but it is not required by $@. | test.py:4:17:4:31 | Str | format "{name!r}, {0}" | +| test.py:18:1:18:45 | format() | Surplus named argument for string format. An argument named 'world' is provided, but it is not required by $@. | test.py:9:17:9:31 | Str | format "{name!r}, {0}" | +| test.py:20:1:20:49 | Attribute() | Surplus named argument for string format. An argument named 'world' is provided, but it is not required by $@. | test.py:4:17:4:31 | Str | format "{name!r}, {0}" | +| test.py:21:1:21:50 | format() | Surplus named argument for string format. An argument named 'world' is provided, but it is not required by $@. | test.py:9:17:9:31 | Str | format "{name!r}, {0}" | +| test.py:45:1:45:35 | format() | Surplus named argument for string format. An argument named 'z' is provided, but it is not required by $@. | test.py:37:14:37:18 | Str | any format used. | +| test.py:45:1:45:35 | format() | Surplus named argument for string format. An argument named 'z' is provided, but it is not required by $@. | test.py:39:14:39:18 | Str | any format used. | +| test.py:46:1:46:34 | Attribute() | Surplus named argument for string format. An argument named 'z' is provided, but it is not required by $@. | test.py:37:14:37:18 | Str | any format used. | +| test.py:46:1:46:34 | Attribute() | Surplus named argument for string format. An argument named 'z' is provided, but it is not required by $@. | test.py:39:14:39:18 | Str | any format used. | diff --git a/python/ql/test/query-tests/Expressions/Formatting/UnusedNamedArgumentIn3101Format.qlref b/python/ql/test/query-tests/Expressions/Formatting/UnusedNamedArgumentIn3101Format.qlref new file mode 100644 index 000000000000..6a77d8910797 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Formatting/UnusedNamedArgumentIn3101Format.qlref @@ -0,0 +1 @@ +Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/Formatting/WrongNameInArgumentsFor3101Format.expected b/python/ql/test/query-tests/Expressions/Formatting/WrongNameInArgumentsFor3101Format.expected new file mode 100644 index 000000000000..4daa9bfa12cb --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Formatting/WrongNameInArgumentsFor3101Format.expected @@ -0,0 +1,2 @@ +| test.py:17:1:17:44 | Attribute() | Missing named argument for string format. Format $@ requires 'name', but it is omitted. | test.py:4:17:4:31 | Str | "{name!r}, {0}" | +| test.py:18:1:18:45 | format() | Missing named argument for string format. Format $@ requires 'name', but it is omitted. | test.py:9:17:9:31 | Str | "{name!r}, {0}" | diff --git a/python/ql/test/query-tests/Expressions/Formatting/WrongNameInArgumentsFor3101Format.qlref b/python/ql/test/query-tests/Expressions/Formatting/WrongNameInArgumentsFor3101Format.qlref new file mode 100644 index 000000000000..e0b308870342 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Formatting/WrongNameInArgumentsFor3101Format.qlref @@ -0,0 +1 @@ +Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/Formatting/WrongNumberArgumentsFor3101Format.expected b/python/ql/test/query-tests/Expressions/Formatting/WrongNumberArgumentsFor3101Format.expected new file mode 100644 index 000000000000..188ee7c4da1d --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Formatting/WrongNumberArgumentsFor3101Format.expected @@ -0,0 +1,6 @@ +| test.py:20:1:20:49 | Attribute() | Too few arguments for string format. Format $@ requires at least 1, but 0 are provided. | test.py:4:17:4:31 | Str | "{name!r}, {0}" | +| test.py:21:1:21:50 | format() | Too few arguments for string format. Format $@ requires at least 1, but 0 are provided. | test.py:9:17:9:31 | Str | "{name!r}, {0}" | +| test.py:23:1:23:32 | Attribute() | Too few arguments for string format. Format $@ requires at least 2, but 1 is provided. | test.py:5:20:5:29 | Str | "{0}, {1}" | +| test.py:24:1:24:33 | format() | Too few arguments for string format. Format $@ requires at least 2, but 1 is provided. | test.py:10:20:10:29 | Str | "{0}, {1}" | +| test.py:26:1:26:32 | Attribute() | Too few arguments for string format. Format $@ requires at least 2, but 1 is provided. | test.py:6:20:6:27 | Str | "{}, {}" | +| test.py:27:1:27:33 | format() | Too few arguments for string format. Format $@ requires at least 2, but 1 is provided. | test.py:11:20:11:27 | Str | "{}, {}" | diff --git a/python/ql/test/query-tests/Expressions/Formatting/WrongNumberArgumentsFor3101Format.qlref b/python/ql/test/query-tests/Expressions/Formatting/WrongNumberArgumentsFor3101Format.qlref new file mode 100644 index 000000000000..130a6525a901 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Formatting/WrongNumberArgumentsFor3101Format.qlref @@ -0,0 +1 @@ +Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/Formatting/test.py b/python/ql/test/query-tests/Expressions/Formatting/test.py new file mode 100755 index 000000000000..e9fd23c8aad6 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Formatting/test.py @@ -0,0 +1,108 @@ +from __future__ import unicode_literals + +mixed_format1 = "{}{1}" +named_format1 = "{name!r}, {0}" +explicit_format1 = "{0}, {1}" +implicit_format1 = "{}, {}" + +mixed_format2 = "{}{1}" +named_format2 = "{name!r}, {0}" +explicit_format2 = "{0}, {1}" +implicit_format2 = "{}, {}" + + +mixed_format1.format("Hello", "World") +format(mixed_format2, "Hello", "World") + +named_format1.format("Hello", world="World") +format(named_format2, "Hello", world="World") + +named_format1.format(name="Hello", world="World") +format(named_format2, name="Hello", world="World") + +explicit_format1.format("Hello") +format(explicit_format2, "Hello") + +implicit_format1.format("Hello") +format(implicit_format2, "Hello") + +explicit_format1.format("Hello", "World", "Extra") +format(explicit_format2, "Hello", "World", "Extra") + +implicit_format1.format("Hello", "World", "Extra") +format(implicit_format2, "Hello", "World", "Extra") + +#OK ODASA-3197 +if cond: + x_or_y = "{x}" +else: + x_or_y = "{y}" + +format(x_or_y, x="x", y="y") +x_or_y.format(x="x", y="y") + +#Still fail for multiple formats +format(x_or_y, x="x", y="y", z="z") +x_or_y.format(x="x", y="y", z="z") + +#False positive reported by customer. -- Verify fix. +"{{}}>".format(html_class) + +"{{0}}{0}".format("X") +"{{{}}}".format("X") + +#Crash JDK regex engine -- unless possessive quantifiers are used. +( + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{" + "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}" +) + +#Make sure nested braces are handled properly: +"ANS_{}=${{{}}}".format(x, xID) + +def foo(msg_width): + stdout.write(u'{}\r{}{:<{}}'.format(1, 2, 3, 4)) + stdout.write(u'{}\r{}{:<{width}}'.format(1, 2, 3, width=msg_width)) + +#Check parsing with punctuation. +"invalid value of type {.__name__}: {}".format(int, 1) + +def varying_format(cond): + fmt = "{}" if cond else "{}, {}" + return fmt.format("hello", "world") diff --git a/python/ql/test/query-tests/Expressions/Regex/BackspaceEscape.expected b/python/ql/test/query-tests/Expressions/Regex/BackspaceEscape.expected new file mode 100644 index 000000000000..1f9bf778bd3e --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/BackspaceEscape.expected @@ -0,0 +1,2 @@ +| test.py:17:12:17:22 | Str | Backspace escape in regular expression at offset 1. | +| test.py:19:12:19:28 | Str | Backspace escape in regular expression at offset 8. | diff --git a/python/ql/test/query-tests/Expressions/Regex/BackspaceEscape.qlref b/python/ql/test/query-tests/Expressions/Regex/BackspaceEscape.qlref new file mode 100644 index 000000000000..2bf85f8a45aa --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/BackspaceEscape.qlref @@ -0,0 +1 @@ +Expressions/Regex/BackspaceEscape.ql diff --git a/python/ql/test/query-tests/Expressions/Regex/DuplicateCharacterInSet.expected b/python/ql/test/query-tests/Expressions/Regex/DuplicateCharacterInSet.expected new file mode 100644 index 000000000000..c79c219256c2 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/DuplicateCharacterInSet.expected @@ -0,0 +1,3 @@ +| test.py:41:12:41:18 | Str | This regular expression includes duplicate character 'A' in a set of characters. | +| test.py:42:12:42:19 | Str | This regular expression includes duplicate character '0' in a set of characters. | +| test.py:43:12:43:21 | Str | This regular expression includes duplicate character '-' in a set of characters. | diff --git a/python/ql/test/query-tests/Expressions/Regex/DuplicateCharacterInSet.qlref b/python/ql/test/query-tests/Expressions/Regex/DuplicateCharacterInSet.qlref new file mode 100644 index 000000000000..f0fc83c214eb --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/DuplicateCharacterInSet.qlref @@ -0,0 +1 @@ +Expressions/Regex/DuplicateCharacterInSet.ql diff --git a/python/ql/test/query-tests/Expressions/Regex/MissingPartSpecialGroup.expected b/python/ql/test/query-tests/Expressions/Regex/MissingPartSpecialGroup.expected new file mode 100644 index 000000000000..e7bbad23899e --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/MissingPartSpecialGroup.expected @@ -0,0 +1,2 @@ +| test.py:22:12:22:29 | Str | Regular expression is missing '?' in named group. | +| test.py:23:12:23:33 | Str | Regular expression is missing '?' in named group. | diff --git a/python/ql/test/query-tests/Expressions/Regex/MissingPartSpecialGroup.qlref b/python/ql/test/query-tests/Expressions/Regex/MissingPartSpecialGroup.qlref new file mode 100644 index 000000000000..faf8f31ad4d6 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/MissingPartSpecialGroup.qlref @@ -0,0 +1 @@ +Expressions/Regex/MissingPartSpecialGroup.ql diff --git a/python/ql/test/query-tests/Expressions/Regex/UnmatchableCaret.expected b/python/ql/test/query-tests/Expressions/Regex/UnmatchableCaret.expected new file mode 100644 index 000000000000..1d6cabfafdcd --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/UnmatchableCaret.expected @@ -0,0 +1,4 @@ +| test.py:4:12:4:19 | Str | This regular expression includes an unmatchable caret at offset 1. | +| test.py:5:12:5:23 | Str | This regular expression includes an unmatchable caret at offset 5. | +| test.py:6:12:6:21 | Str | This regular expression includes an unmatchable caret at offset 2. | +| test.py:74:12:74:27 | Str | This regular expression includes an unmatchable caret at offset 8. | diff --git a/python/ql/test/query-tests/Expressions/Regex/UnmatchableCaret.qlref b/python/ql/test/query-tests/Expressions/Regex/UnmatchableCaret.qlref new file mode 100644 index 000000000000..161fd59f7f28 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/UnmatchableCaret.qlref @@ -0,0 +1 @@ +Expressions/Regex/UnmatchableCaret.ql diff --git a/python/ql/test/query-tests/Expressions/Regex/UnmatchableDollar.expected b/python/ql/test/query-tests/Expressions/Regex/UnmatchableDollar.expected new file mode 100644 index 000000000000..7771654c79bf --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/UnmatchableDollar.expected @@ -0,0 +1,4 @@ +| test.py:29:12:29:19 | Str | This regular expression includes an unmatchable dollar at offset 3. | +| test.py:30:12:30:23 | Str | This regular expression includes an unmatchable dollar at offset 3. | +| test.py:31:12:31:20 | Str | This regular expression includes an unmatchable dollar at offset 2. | +| test.py:75:12:75:26 | Str | This regular expression includes an unmatchable dollar at offset 3. | diff --git a/python/ql/test/query-tests/Expressions/Regex/UnmatchableDollar.qlref b/python/ql/test/query-tests/Expressions/Regex/UnmatchableDollar.qlref new file mode 100644 index 000000000000..b162342922c5 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/UnmatchableDollar.qlref @@ -0,0 +1 @@ +Expressions/Regex/UnmatchableDollar.ql diff --git a/python/ql/test/query-tests/Expressions/Regex/options b/python/ql/test/query-tests/Expressions/Regex/options new file mode 100644 index 000000000000..b91afde07678 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=2 diff --git a/python/ql/test/query-tests/Expressions/Regex/test.py b/python/ql/test/query-tests/Expressions/Regex/test.py new file mode 100644 index 000000000000..6a5742a86136 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/Regex/test.py @@ -0,0 +1,135 @@ +import re + +#Unmatchable caret +re.compile(b' ^abc') +re.compile(b"(?s) ^abc") +re.compile(b"\[^123]") + +#Likely false positives for unmatchable caret +re.compile(b"[^123]") +re.compile(b"[123^]") +re.sub(b'(?m)^(?!$)', indent*' ', s) +re.compile(b"()^abc") +re.compile(b"(?:(?:\n\r?)|^)( *)\S") +re.compile(b"^diff (?:-r [0-9a-f]+ ){1,2}(.*)$") + +#Backspace escape +re.compile(br"[\b\t ]") # Should warn +re.compile(br"E\d+\b.*") # Fine +re.compile(br"E\d+\b[ \b\t]") #Both + +#Missing part in named group +re.compile(br'(P[\w]+)') +re.compile(br'(_(P[\w]+)|)') +#This is OK... +re.compile(br'(?P\w+)') + + +#Unmatchable dollar +re.compile(b"abc$ ") +re.compile(b"abc$ (?s)") +re.compile(b"\[$] ") + +#Likely false positives for unmatchable dollar +re.compile(b"[$] ") +re.compile(b"\$ ") +re.compile(b"abc$(?m)") +re.compile(b"abc$()") + + +#Duplicate character in set +re.compile(b"[AA]") +re.compile(b"[000]") +re.compile(b"[-0-9-]") + +#Possible false positives +re.compile(b"[S\S]") +re.compile(b"[0\000]") +re.compile(b"[\0000]") +re.compile(b"[^^]") +re.compile(b"[-0-9]") +re.compile(b"[]]") +re.compile(b"^^^x.*") +re.compile(b".*x$$$") +re.compile(b"x*^y") +re.compile(b"x$y*") + +# False positive for unmatchable caret +re.compile(br'(?!DEFAULT_PREFS)(?!CAN_SET_ANON)^[A-Z_]+$') + +#Equivalent for unmatchable dollar +re.compile(br'^[A-Z_]+(?!DEFAULT_PREFS)(?!CAN_SET_ANON)$') + +#And for negative look-behind assertions +re.compile(br'(?[+-]?) + (?: (?P \d+ (?:\.\d*)? ) \s* [wW] )? \s* + (?: (?P \d+ (?:\.\d*)? ) \s* [dD] )? \s* + (?: (?P \d+ (?:\.\d*)? ) \s* [hH] )? \s* + (?: (?P \d+ (?:\.\d*)? ) \s* [mM] )? \s* + (?: (?P \d+ (?:\.\d*)? ) \s* [sS] )? \s* + $ + ''', + flags=re.VERBOSE) + +REGEX3 = re.compile(r''' + ^\s* + (?P[+-]?) + (?: (?P \d+ (?:\.\d*)? ) \s* [wW] )? \s* + (?: (?P \d+ (?:\.\d*)? ) \s* [dD] )? \s* + (?: (?P \d+ (?:\.\d*)? ) \s* [hH] )? \s* + (?: (?P \d+ (?:\.\d*)? ) \s* [mM] )? \s* + (?: (?P \d+ (?:\.\d*)? ) \s* [sS] )? \s* + $ + ''', + re.VERBOSE) + +#ODASA-6780 +DYLIB_RE = re.compile(r"""(?x) +(?P^.*)(?:^|/) +(?P + (?P\w+?) + (?:\.(?P[^._]+))? + (?:_(?P[^._]+))? + \.dylib$ +) +""") + +#ODASA-6786 +VERBOSE_REGEX = r""" + \[ # [ + (?P
    [^]]+) # very permissive! + \] # ] + """ + +# Compiled regular expression marking it as verbose +ODASA_6786 = re.compile(VERBOSE_REGEX, re.VERBOSE) diff --git a/python/ql/test/query-tests/Expressions/callable/NonCallableCalled.expected b/python/ql/test/query-tests/Expressions/callable/NonCallableCalled.expected new file mode 100644 index 000000000000..b7fb4e56b435 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/callable/NonCallableCalled.expected @@ -0,0 +1,5 @@ +| test.py:16:5:16:12 | non() | Call to a $@ of $@. | test.py:15:11:15:23 | NonCallable() | non-callable | test.py:3:1:3:26 | class NonCallable | class NonCallable | +| test.py:17:5:17:8 | Tuple() | Call to a $@ of $@. | test.py:17:5:17:6 | Tuple | non-callable | file://:Compiled Code:0:0:0:0 | builtin-class tuple | builtin-class tuple | +| test.py:18:5:18:8 | List() | Call to a $@ of $@. | test.py:18:5:18:6 | List | non-callable | file://:Compiled Code:0:0:0:0 | builtin-class list | builtin-class list | +| test.py:26:9:26:16 | non() | Call to a $@ of $@. | test.py:15:11:15:23 | NonCallable() | non-callable | test.py:3:1:3:26 | class NonCallable | class NonCallable | +| test.py:47:12:47:27 | NotImplemented() | Call to a $@ of $@. | test.py:47:12:47:25 | NotImplemented | non-callable | file://:Compiled Code:0:0:0:0 | builtin-class NotImplementedType | builtin-class NotImplementedType | diff --git a/python/ql/test/query-tests/Expressions/callable/NonCallableCalled.qlref b/python/ql/test/query-tests/Expressions/callable/NonCallableCalled.qlref new file mode 100644 index 000000000000..ea8577e6f9fb --- /dev/null +++ b/python/ql/test/query-tests/Expressions/callable/NonCallableCalled.qlref @@ -0,0 +1 @@ +Expressions/NonCallableCalled.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/callable/test.py b/python/ql/test/query-tests/Expressions/callable/test.py new file mode 100644 index 000000000000..6ab04558064c --- /dev/null +++ b/python/ql/test/query-tests/Expressions/callable/test.py @@ -0,0 +1,48 @@ + +#Non-callable called +class NonCallable(object): + pass + +class MaybeCallable(Unknown, object): + pass + +class IsCallable(object): + + def __call__(self): + pass + +def call_non_callable(arg): + non = NonCallable() + non(arg) + ()() + []() + dont_know = MaybeCallable() + dont_know() # Not a violation + ok = IsCallable() + ok() + if hasattr(non, "__call__"): + non(arg) # OK due to guard + if hasattr(non, "__init__"): + non(arg) # Not OK due to wrong guard + +import six + +#ODASA-4812 +def call_six_guarded(arg=None): + # If it's a callable, call it + if six.callable(arg): + arg = arg() + +#ODASA-6261 +def experimental_jit_scope(compile_ops=True, separate_compiled_gradients=False): + if callable(compile_ops): + def xla_compile(node_def): + return attr_value_pb2.AttrValue(b=compile_ops(node_def)) + +def foo(): + #This is so common, we have a different query for it + raise NotImplemented() + +def bar(): + return NotImplemented() + diff --git a/python/ql/test/query-tests/Expressions/comparisons/UselessComparisonTest.expected b/python/ql/test/query-tests/Expressions/comparisons/UselessComparisonTest.expected new file mode 100644 index 000000000000..f2f9a79a6579 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/comparisons/UselessComparisonTest.expected @@ -0,0 +1,10 @@ +| test.py:6:8:6:13 | Compare | Test is always true, because of $@ | test.py:4:8:4:12 | Compare | this condition | +| test.py:8:8:8:13 | Compare | Test is always true, because of $@ | test.py:4:17:4:21 | Compare | this condition | +| test.py:13:16:13:22 | Compare | Test is always false, because of $@ | test.py:11:12:11:17 | Compare | this condition | +| test.py:15:14:15:18 | Compare | Test is always true, because of $@ | test.py:11:12:11:17 | Compare | this condition | +| test.py:27:8:27:13 | Compare | Test is always true, because of $@ | test.py:25:8:25:12 | Compare | this condition | +| test.py:30:12:30:18 | Compare | Test is always false, because of $@ | test.py:25:17:25:23 | Compare | this condition | +| test.py:49:8:49:12 | Compare | Test is always false, because of $@ | test.py:47:8:47:50 | Compare | this condition | +| test.py:73:14:73:26 | Compare | Test is always true, because of $@ | test.py:71:8:71:19 | Compare | this condition | +| test.py:79:14:79:46 | Compare | Test is always true, because of $@ | test.py:77:8:77:19 | Compare | this condition | +| test.py:85:10:85:42 | Compare | Test is always true, because of $@ | test.py:83:8:83:19 | Compare | this condition | diff --git a/python/ql/test/query-tests/Expressions/comparisons/UselessComparisonTest.qlref b/python/ql/test/query-tests/Expressions/comparisons/UselessComparisonTest.qlref new file mode 100644 index 000000000000..fb7f75f9f615 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/comparisons/UselessComparisonTest.qlref @@ -0,0 +1 @@ +Expressions/Comparisons/UselessComparisonTest.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/comparisons/test.py b/python/ql/test/query-tests/Expressions/comparisons/test.py new file mode 100644 index 000000000000..b0bb709e2c34 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/comparisons/test.py @@ -0,0 +1,88 @@ + + +def f(w, x, y, z): + if x < 0 or z < 0: + raise Exception() + if x >= 0: # Useless test due to x < 0 being false + y += 1 + if z >= 0: # Useless test due to z < 0 being false + y += 1 + while w >= 0: + if y < 10: + z += 1 + if y == 15: # Useless test due to y < 10 being true + z += 1 + elif y > 7: # Useless test + y -= 1 + if y < 10: + y += 1 + if y < 12: #A useless test, but too complex to infer. + pass + if not y != 5 and z > 0: + w = 0 if y < 3 else 1 #Useless test as y is 5 + +def g(w, x, y, z): + if w < x or y < z+2: + raise Exception() + if w >= x: # Useless test due to w < x being false + pass + if cond: + if z > y-2: # Useless test due to y < z+2 being false + y += 1 + else: + if z >= y-2: # Not a useless test. + y += 1 + +#ODASA-5643 +def validate_series(start, end): + # Check that the values 'make sense' + if end < start: + raise click.BadParameter('The start value must be less than the end value.') + if start == end: + raise click.BadParameter('The start value and the end value most not be the same.') + return start, end + +#Overflow +def medium1(x, y): + if x + 1000000000000000 > y + 1000000000000000: + return + if x > y: # Redundant + pass + +def medium2(x, y): + if x + 1000000000000000 > y + 1000000000000001: + return + if x > y: # Not redundant + pass + +def big1(x, y): + if x + 10000000000000000 > y + 10000000000000000: + return + if x > y: # Redundant (but cannot be sure due to FP rounding errors) + pass + +def big2(x, y): + if x + 10000000000000000 > y + 10000000000000001: + return + if x > y: # Not redundant (but might appear to be due to FP rounding errors) + pass + +def odasa6782_v1(protocol): + if protocol < 0: + protocol = HIGHEST_PROTOCOL + elif not 0 <= protocol: + raise ValueError() + +def odasa6782_v2(protocol): + if protocol < 0: + protocol = HIGHEST_PROTOCOL + elif not 0 <= protocol <= HIGHEST_PROTOCOL: + raise ValueError() + +def odasa6782_v3(protocol): + if protocol < 0: + protocol = HIGHEST_PROTOCOL + elif 0 <= protocol <= HIGHEST_PROTOCOL: + pass + else: + raise ValueError() diff --git a/python/ql/test/query-tests/Expressions/eq/IncorrectComparisonUsingIs.expected b/python/ql/test/query-tests/Expressions/eq/IncorrectComparisonUsingIs.expected new file mode 100644 index 000000000000..6138c2efb683 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/eq/IncorrectComparisonUsingIs.expected @@ -0,0 +1 @@ +| expressions_test.py:46:4:46:21 | Compare | Values compared using 'is' when equivalence is not the same as identity. Use '==' instead. | diff --git a/python/ql/test/query-tests/Expressions/eq/IncorrectComparisonUsingIs.qlref b/python/ql/test/query-tests/Expressions/eq/IncorrectComparisonUsingIs.qlref new file mode 100644 index 000000000000..73123cf76281 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/eq/IncorrectComparisonUsingIs.qlref @@ -0,0 +1 @@ +Expressions/IncorrectComparisonUsingIs.ql diff --git a/python/ql/test/query-tests/Expressions/eq/NonPortableComparisonUsingIs.expected b/python/ql/test/query-tests/Expressions/eq/NonPortableComparisonUsingIs.expected new file mode 100644 index 000000000000..bf7e29279e26 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/eq/NonPortableComparisonUsingIs.expected @@ -0,0 +1,2 @@ +| expressions_test.py:51:4:51:11 | Compare | The result of this comparison with 'is' may differ between implementations of Python. | +| expressions_test.py:56:4:56:16 | Compare | The result of this comparison with 'is' may differ between implementations of Python. | diff --git a/python/ql/test/query-tests/Expressions/eq/NonPortableComparisonUsingIs.qlref b/python/ql/test/query-tests/Expressions/eq/NonPortableComparisonUsingIs.qlref new file mode 100644 index 000000000000..13c08534293c --- /dev/null +++ b/python/ql/test/query-tests/Expressions/eq/NonPortableComparisonUsingIs.qlref @@ -0,0 +1 @@ +Expressions/NonPortableComparisonUsingIs.ql diff --git a/python/ql/test/query-tests/Expressions/eq/expressions_test.py b/python/ql/test/query-tests/Expressions/eq/expressions_test.py new file mode 100644 index 000000000000..2ffac276553f --- /dev/null +++ b/python/ql/test/query-tests/Expressions/eq/expressions_test.py @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +#ODASA-4519 +#OK as we are using identity tests for unique objects +V2 = "v2" +V3 = "v3" + +class C: + def __init__(self, c): + if c: + self.version = V2 + else: + self.version = V3 + + def meth(self): + if self.version is V2: #FP here. + pass + + +#Using 'is' when should be using '==' +s = "Hello " + "World" +if "Hello World" is s: + print ("OK") + +#This is OK in CPython, but may not be portable +s = str(7) +if "7" is s: + print ("OK") + +#And some data flow +CONSTANT = 20 +if x is CONSTANT: + print ("OK") + +#This is OK +x = object() +y = object() +if x is y: + print ("Very surprising!") + +#This is also OK +if s is None: + print ("Also surprising") + +#Portable is comparisons +def f(arg): + arg is () + arg is 0 + +class C(object): + def __eq__(self, other): + ''' + 'is' must be fine here. + ''' + return self is other + +#Was FP -- https://github.com/lgtmhq/lgtm-queries/issues/13 +def both_sides_known(zero_based="auto", query_id=False): + if query_id and zero_based == "auto": + zero_based = True + if zero_based is False: # False positive here + pass + diff --git a/python/ql/test/query-tests/Expressions/general/CompareConstants.expected b/python/ql/test/query-tests/Expressions/general/CompareConstants.expected new file mode 100644 index 000000000000..ea8645f523c5 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/CompareConstants.expected @@ -0,0 +1,2 @@ +| compare.py:12:1:12:6 | Compare | Comparison of constants; use 'True' or 'False' instead. | +| compare.py:13:1:13:6 | Compare | Comparison of constants; use 'True' or 'False' instead. | diff --git a/python/ql/test/query-tests/Expressions/general/CompareConstants.qlref b/python/ql/test/query-tests/Expressions/general/CompareConstants.qlref new file mode 100644 index 000000000000..0e2ab115eeec --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/CompareConstants.qlref @@ -0,0 +1 @@ +Expressions/CompareConstants.ql diff --git a/python/ql/test/query-tests/Expressions/general/CompareIdenticalValues.expected b/python/ql/test/query-tests/Expressions/general/CompareIdenticalValues.expected new file mode 100644 index 000000000000..9132b5db786b --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/CompareIdenticalValues.expected @@ -0,0 +1,2 @@ +| compare.py:8:1:8:6 | Compare | Comparison of identical values; use cmath.isnan() if testing for not-a-number. | +| compare.py:9:1:9:10 | Compare | Comparison of identical values; use cmath.isnan() if testing for not-a-number. | diff --git a/python/ql/test/query-tests/Expressions/general/CompareIdenticalValues.qlref b/python/ql/test/query-tests/Expressions/general/CompareIdenticalValues.qlref new file mode 100644 index 000000000000..4bc0ec69fc04 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/CompareIdenticalValues.qlref @@ -0,0 +1 @@ +Expressions/CompareIdenticalValues.ql diff --git a/python/ql/test/query-tests/Expressions/general/CompareIdenticalValuesMissingSelf.expected b/python/ql/test/query-tests/Expressions/general/CompareIdenticalValuesMissingSelf.expected new file mode 100644 index 000000000000..331f591b2d41 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/CompareIdenticalValuesMissingSelf.expected @@ -0,0 +1 @@ +| compare.py:22:12:22:17 | Compare | Comparison of identical values; may be missing 'self'. | diff --git a/python/ql/test/query-tests/Expressions/general/CompareIdenticalValuesMissingSelf.qlref b/python/ql/test/query-tests/Expressions/general/CompareIdenticalValuesMissingSelf.qlref new file mode 100644 index 000000000000..f19a0dee4364 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/CompareIdenticalValuesMissingSelf.qlref @@ -0,0 +1 @@ +Expressions/CompareIdenticalValuesMissingSelf.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/general/ContainsNonContainer.expected b/python/ql/test/query-tests/Expressions/general/ContainsNonContainer.expected new file mode 100644 index 000000000000..cf6d78d0d36b --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/ContainsNonContainer.expected @@ -0,0 +1,2 @@ +| expressions_test.py:89:8:89:15 | Compare | This test may raise an Exception as the $@ may be of non-container class $@. | expressions_test.py:88:11:88:17 | ControlFlowNode for XIter() | target | expressions_test.py:77:1:77:20 | class XIter | XIter | +| expressions_test.py:91:8:91:19 | Compare | This test may raise an Exception as the $@ may be of non-container class $@. | expressions_test.py:88:11:88:17 | ControlFlowNode for XIter() | target | expressions_test.py:77:1:77:20 | class XIter | XIter | diff --git a/python/ql/test/query-tests/Expressions/general/ContainsNonContainer.qlref b/python/ql/test/query-tests/Expressions/general/ContainsNonContainer.qlref new file mode 100644 index 000000000000..71df405e72c1 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/ContainsNonContainer.qlref @@ -0,0 +1 @@ +Expressions/ContainsNonContainer.ql diff --git a/python/ql/test/query-tests/Expressions/general/DuplicateKeyInDictionaryLiteral.expected b/python/ql/test/query-tests/Expressions/general/DuplicateKeyInDictionaryLiteral.expected new file mode 100644 index 000000000000..70673ebc7c73 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/DuplicateKeyInDictionaryLiteral.expected @@ -0,0 +1,2 @@ +| expressions_test.py:3:14:3:14 | IntegerLiteral | Dictionary key 1 is subsequently $@. | expressions_test.py:4:14:4:14 | IntegerLiteral | overwritten | +| expressions_test.py:5:14:5:17 | Str | Dictionary key 'a' is subsequently $@. | expressions_test.py:6:14:6:17 | Str | overwritten | diff --git a/python/ql/test/query-tests/Expressions/general/DuplicateKeyInDictionaryLiteral.qlref b/python/ql/test/query-tests/Expressions/general/DuplicateKeyInDictionaryLiteral.qlref new file mode 100644 index 000000000000..a1bb71098829 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/DuplicateKeyInDictionaryLiteral.qlref @@ -0,0 +1 @@ +Expressions/DuplicateKeyInDictionaryLiteral.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/general/EqualsNone.expected b/python/ql/test/query-tests/Expressions/general/EqualsNone.expected new file mode 100644 index 000000000000..b3802afa0c79 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/EqualsNone.expected @@ -0,0 +1 @@ +| expressions_test.py:115:12:115:22 | Compare | Testing for None should use the 'is' operator. | diff --git a/python/ql/test/query-tests/Expressions/general/EqualsNone.qlref b/python/ql/test/query-tests/Expressions/general/EqualsNone.qlref new file mode 100644 index 000000000000..8d9699258e25 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/EqualsNone.qlref @@ -0,0 +1 @@ +Expressions/EqualsNone.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/general/ExpectedMappingForFormatString.expected b/python/ql/test/query-tests/Expressions/general/ExpectedMappingForFormatString.expected new file mode 100644 index 000000000000..ab1ab1a79151 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/ExpectedMappingForFormatString.expected @@ -0,0 +1 @@ +| str_fmt_test.py:5:26:5:26 | x | Right hand side of a % operator must be a mapping, not class $@. | file://:Compiled Code:0:0:0:0 | builtin-class list | list | diff --git a/python/ql/test/query-tests/Expressions/general/ExpectedMappingForFormatString.qlref b/python/ql/test/query-tests/Expressions/general/ExpectedMappingForFormatString.qlref new file mode 100644 index 000000000000..83e92584ef27 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/ExpectedMappingForFormatString.qlref @@ -0,0 +1 @@ +Expressions/ExpectedMappingForFormatString.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/general/ExplcitCallToDel.expected b/python/ql/test/query-tests/Expressions/general/ExplcitCallToDel.expected new file mode 100644 index 000000000000..909c9d8ccc84 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/ExplcitCallToDel.expected @@ -0,0 +1 @@ +Failure \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/general/ExplicitCallToDel.expected b/python/ql/test/query-tests/Expressions/general/ExplicitCallToDel.expected new file mode 100644 index 000000000000..cd1c27f10e0d --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/ExplicitCallToDel.expected @@ -0,0 +1,2 @@ +| expressions_test.py:37:1:37:11 | Attribute() | The __del__ special method is called explicitly. | +| expressions_test.py:133:9:133:22 | Attribute() | The __del__ special method is called explicitly. | diff --git a/python/ql/test/query-tests/Expressions/general/ExplicitCallToDel.qlref b/python/ql/test/query-tests/Expressions/general/ExplicitCallToDel.qlref new file mode 100644 index 000000000000..932f1a3d366d --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/ExplicitCallToDel.qlref @@ -0,0 +1 @@ +Expressions/ExplicitCallToDel.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/general/HashedButNoHash.expected b/python/ql/test/query-tests/Expressions/general/HashedButNoHash.expected new file mode 100644 index 000000000000..69bc7a7db393 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/HashedButNoHash.expected @@ -0,0 +1 @@ +| expressions_test.py:42:20:42:25 | unhash | This $@ of $@ is unhashable. | expressions_test.py:41:32:41:37 | ControlFlowNode for list() | instance | file://:Compiled Code:0:0:0:0 | builtin-class list | list | diff --git a/python/ql/test/query-tests/Expressions/general/HashedButNoHash.qlref b/python/ql/test/query-tests/Expressions/general/HashedButNoHash.qlref new file mode 100644 index 000000000000..ee53e3674999 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/HashedButNoHash.qlref @@ -0,0 +1 @@ +Expressions/HashedButNoHash.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/general/UnnecessaryLambda.expected b/python/ql/test/query-tests/Expressions/general/UnnecessaryLambda.expected new file mode 100644 index 000000000000..dd8db3d41542 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/UnnecessaryLambda.expected @@ -0,0 +1,6 @@ +| expressions_test.py:11:1:11:42 | Lambda | This 'lambda' is just a simple wrapper around a callable object. Use that object directly. | +| expressions_test.py:12:1:12:44 | Lambda | This 'lambda' is just a simple wrapper around a callable object. Use that object directly. | +| expressions_test.py:13:1:13:46 | Lambda | This 'lambda' is just a simple wrapper around a callable object. Use that object directly. | +| expressions_test.py:141:1:141:22 | Lambda | This 'lambda' is just a simple wrapper around a callable object. Use that object directly. | +| expressions_test.py:142:1:142:29 | Lambda | This 'lambda' is just a simple wrapper around a callable object. Use that object directly. | +| expressions_test.py:149:16:149:34 | Lambda | This 'lambda' is just a simple wrapper around a callable object. Use that object directly. | diff --git a/python/ql/test/query-tests/Expressions/general/UnnecessaryLambda.qlref b/python/ql/test/query-tests/Expressions/general/UnnecessaryLambda.qlref new file mode 100644 index 000000000000..49b3873f83ce --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/UnnecessaryLambda.qlref @@ -0,0 +1 @@ +Expressions/UnnecessaryLambda.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/general/UnsupportedFormatCharacter.expected b/python/ql/test/query-tests/Expressions/general/UnsupportedFormatCharacter.expected new file mode 100644 index 000000000000..0e4fdb3d5654 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/UnsupportedFormatCharacter.expected @@ -0,0 +1 @@ +| str_fmt_test.py:8:12:8:16 | Str | Invalid conversion specifier at index 0 of '%Z'. | diff --git a/python/ql/test/query-tests/Expressions/general/UnsupportedFormatCharacter.qlref b/python/ql/test/query-tests/Expressions/general/UnsupportedFormatCharacter.qlref new file mode 100644 index 000000000000..3cb459229e4a --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/UnsupportedFormatCharacter.qlref @@ -0,0 +1 @@ +Expressions/UnsupportedFormatCharacter.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/general/WrongNumberArgumentsForFormat.expected b/python/ql/test/query-tests/Expressions/general/WrongNumberArgumentsForFormat.expected new file mode 100644 index 000000000000..3e83aa4bb0d8 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/WrongNumberArgumentsForFormat.expected @@ -0,0 +1,2 @@ +| str_fmt_test.py:11:11:11:34 | BinaryExpr | Wrong number of $@ for string format. Format $@ takes 2, but 3 are provided. | str_fmt_test.py:11:23:11:33 | Tuple | arguments | str_fmt_test.py:11:11:11:18 | Str | %s %s | +| str_fmt_test.py:14:11:14:23 | BinaryExpr | Wrong number of $@ for string format. Format $@ takes 1, but 2 are provided. | str_fmt_test.py:13:13:13:21 | Tuple | arguments | str_fmt_test.py:12:14:12:19 | Str | %hd | diff --git a/python/ql/test/query-tests/Expressions/general/WrongNumberArgumentsForFormat.qlref b/python/ql/test/query-tests/Expressions/general/WrongNumberArgumentsForFormat.qlref new file mode 100644 index 000000000000..0d127e1b618b --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/WrongNumberArgumentsForFormat.qlref @@ -0,0 +1 @@ +Expressions/WrongNumberArgumentsForFormat.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/general/_private.py b/python/ql/test/query-tests/Expressions/general/_private.py new file mode 100644 index 000000000000..f7d0e50ba20a --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/_private.py @@ -0,0 +1,4 @@ +#Private modules, still needs docstrings + +def f(x, y): + 'Doc string' diff --git a/python/ql/test/query-tests/Expressions/general/compare.py b/python/ql/test/query-tests/Expressions/general/compare.py new file mode 100644 index 000000000000..141b5e6a0286 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/compare.py @@ -0,0 +1,26 @@ + +#OK +a = b = 1 +a == b +a.x == b.x + +#Same variables +a == a +a.x == a.x + +#Compare constants +1 == 1 +1 == 2 + +#Maybe missing self +class X(object): + + def __init__(self, x): + self.x = x + + def missing_self(self, x): + if x == x: + print ("Yes") + +#Compare constants in assert -- ok +assert(1 == 1) diff --git a/python/ql/test/query-tests/Expressions/general/expressions_test.py b/python/ql/test/query-tests/Expressions/general/expressions_test.py new file mode 100644 index 000000000000..7770e326e2c9 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/expressions_test.py @@ -0,0 +1,218 @@ +#encoding: utf-8 +def dup_key(): + return { 1: -1, + 1: -2, + u'a' : u'A', + u'a' : u'B' + } + +def simple_func(*args, **kwrgs): pass +#Unnecessary lambdas +lambda arg0, arg1: simple_func(arg0, arg1) +lambda arg0, *arg1: simple_func(arg0, *arg1) +lambda arg0, **arg1: simple_func(arg0, **arg1) +# these lambdas are_ necessary +lambda arg0, arg1=1: simple_func(arg0, arg1) +lambda arg0, arg1: simple_func(arg0, *arg1) +lambda arg0, arg1: simple_func(arg0, **arg1) +lambda arg0, *arg1: simple_func(arg0, arg1) +lambda arg0, **arg1: simple_func(arg0, arg1) + +#Non-callable called +class NonCallable(object): + pass + +class MaybeCallable(Unknown, object): + pass + +def call_non_callable(arg): + non = NonCallable() + non(arg) + ()() + []() + dont_know = MaybeCallable() + dont_know() # Not a violation + +#Explicit call to __del__ +x.__del__() + +#Unhashable object +def func(): + mapping = dict(); unhash = list() + return mapping[unhash] + +#Using 'is' when should be using '==' +s = "Hello " + "World" +if "Hello World" is s: + print ("OK") + +#This is OK in CPython, but may not be portable +s = str(7) +if "7" is s: + print ("OK") + +#And some data flow +CONSTANT = 20 +if x is CONSTANT: + print ("OK") + +#This is OK +x = object() +y = object() +if x is y: + print ("Very surprising!") + +#This is also OK +if s is None: + print ("Also surprising") + +#Portable is comparisons +def f(arg): + arg is () + arg is 0 + arg is '' + +#Non-container + +class XIter(object): + #Support both 2 and 3 form of next, but omit __iter__ method + + def __next__(self): + pass + + def next(self): + pass + +def non_container(): + + seq = XIter() + if 1 in seq: + pass + if 1 not in seq: + pass + +#Container inheriting from builtin +class MyDict(dict): + pass + +class MySequence(UnresolvablebaseClass): + pass + +def is_container(): + mapping = MyDict() + if 1 in mapping: + pass + seq = MySequence() + if 1 in seq: + pass + seq = None + if seq is not None and 1 in seq: + pass + +#Equals none + +def x(arg): + return arg == None + +class NotMyDict(object): + + def f(self): + super(MyDict, self).f() + +# class defining __del__ +class Test(object): + def __del__(self): + pass + +# subclass +class SubTest(Test): + def __del__(self): + # This is permitted and required. + Test.__del__(self) + # This is a violation. + self.__del__() + # This is an alternate syntax for the super() call, and hence OK. + super(SubTest, self).__del__() + # This is the Python 3 spelling of the same. + super().__del__() + +#Some more lambdas +#Unnecessary lambdas +lambda arg0: len(arg0) +lambda arg0: XIter.next(arg0) +class UL(object): + + def f(self, x): + pass + + def g(self): + return lambda x: self.f(x) + +# these lambdas are necessary +lambda arg0: XIter.next(arg0, arg1) +lambda arg0: func()(arg0) +lambda arg0, arg1: arg0.meth(arg0, arg1) + +#Cannot flag lists as unhashable if the object +#we are subscripting may be a numpy array +def func(maybe_numpy): + unhash = list() + return maybe_numpy[unhash] + +#Guarded non-callable called +def guarded_non_callable(cond): + if cond: + val = [] + else: + val = func + if hasattr(val, "__call__"): + x(1) + + +#ODASA-4056 +def format_string(s, formatter='minimal'): + """Format the given string using the given formatter.""" + if not callable(formatter): + formatter = get_formatter_for_name(formatter) + if formatter is None: + output = s + else: + output = formatter(s) + return output + +#ODASA-4614 +def f(x): + d = {} + d['one'] = {} + d['two'] = 0 + return x[d['two']] + +#ODASA-4055 +class C: + + def _internal(arg): + # arg is not a C + def wrapper(args): + return arg(args) + return wrapper + + @_internal + def method(self, *args): + pass + +#ODASA-4689 +class StrangeIndex: + def __getitem__(self,index): + return 1 + +x = StrangeIndex(); +print(x[{'a': 'b'}]) + +def not_dup_key(): + return { u'a' : 0, + b'a' : 0, + u"😄" : 1, + u"😅" : 2, + u"😆" : 3 + } + diff --git a/python/ql/test/query-tests/Expressions/general/str_fmt_test.py b/python/ql/test/query-tests/Expressions/general/str_fmt_test.py new file mode 100644 index 000000000000..e941b842c319 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/str_fmt_test.py @@ -0,0 +1,55 @@ + + +def expected_mapping_for_fmt_string(): + x = [ u'list', u'not', u'mapping' ] + print (u"%(name)s" % x) + +def unsupported_format_char(arg): + print (u"%Z" % arg) + +def wrong_arg_count_format(arg): + print(u"%s %s" % (arg, arg, 0)) + format = u"%hd" + args = (1, u'foo') + print(format % args) + + +def ok(): + # allowable length modifiers + print(u"%hd %ld %Ld" % (1,2,3)) + + # multiple adjacent % characters + print(u"%%%s" % u"(foo)s") + + # '.' without trailing digits + print(u"%2.d" % 3) + + # a list is OK as an argument to %s + print(u"%s is a list" % [1,2,3,4]) + + # pretty much everything + print(u"%(var)#0+- 8.4hd" % dict(var=44)) + + #This one from PyPy tests + print(u"%()s" % { u'' : u'Hi' }) + +#ODASA 6401 +out = '\n' +out+= ' \n' + +light = out % (az, al) + +#Example from CPython +def f(k, v): + print(' %-40s%a,' % ('%a:' % k, v)) + +#Context sensitive +def g(a, b): + return a % b + +g("%s", 1) +g("%s %s", (1, 2)) + +#Make sure we handle all format characters +"%b %d %i %o %u %x %X %e %E %f %F %g %G %c %r %s" % t() + diff --git a/python/ql/test/query-tests/Expressions/strings/UnintentionalImplicitStringConcatenation.expected b/python/ql/test/query-tests/Expressions/strings/UnintentionalImplicitStringConcatenation.expected new file mode 100644 index 000000000000..1b8b1421f80b --- /dev/null +++ b/python/ql/test/query-tests/Expressions/strings/UnintentionalImplicitStringConcatenation.expected @@ -0,0 +1,4 @@ +| test.py:17:9:18:18 | Str | Implicit string concatenation. Maybe missing a comma? | +| test.py:23:9:24:18 | Str | Implicit string concatenation. Maybe missing a comma? | +| test.py:33:9:34:30 | Str | Implicit string concatenation. Maybe missing a comma? | +| test.py:35:9:36:18 | Str | Implicit string concatenation. Maybe missing a comma? | diff --git a/python/ql/test/query-tests/Expressions/strings/UnintentionalImplicitStringConcatenation.qlref b/python/ql/test/query-tests/Expressions/strings/UnintentionalImplicitStringConcatenation.qlref new file mode 100644 index 000000000000..c305fd129f8b --- /dev/null +++ b/python/ql/test/query-tests/Expressions/strings/UnintentionalImplicitStringConcatenation.qlref @@ -0,0 +1 @@ +Expressions/UnintentionalImplicitStringConcatenation.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/strings/test.py b/python/ql/test/query-tests/Expressions/strings/test.py new file mode 100644 index 000000000000..15b3c9216e33 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/strings/test.py @@ -0,0 +1,46 @@ + +def test(): + single_string = [ + "foo" + "bar" + "/usr/local" + "/usr/bin" + ] + explict_concat = [ + "foo" + + "bar", + "/usr/local", + "/usr/bin" + ] + error1 = [ + "foo", + "/usr/local" + "/usr/bin" + ] + error2 = [ + "foo" + + "bar", + "/usr/local" + "/usr/bin" + ] + +#Examples from documentation + +def unclear(): + # Returns [ "first part of long string and the second part", "/usr/local/usr/bin" ] + return [ + + "first part of long string" + " and the second part", + "/usr/local" + "/usr/bin" + ] + +def clarified(): + # Returns [ "first part of long string and the second part", "/usr/local", "/usr/bin" ] + return [ + "first part of long string" + + " and the second part", + "/usr/local", + "/usr/bin" + ] diff --git a/python/ql/test/query-tests/Expressions/super/CallToSuperWrongClass.expected b/python/ql/test/query-tests/Expressions/super/CallToSuperWrongClass.expected new file mode 100644 index 000000000000..66508e08d708 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/super/CallToSuperWrongClass.expected @@ -0,0 +1 @@ +| test.py:10:9:10:27 | super() | First argument to super() should be NotMyDict. | diff --git a/python/ql/test/query-tests/Expressions/super/CallToSuperWrongClass.qlref b/python/ql/test/query-tests/Expressions/super/CallToSuperWrongClass.qlref new file mode 100644 index 000000000000..c3beeaede04b --- /dev/null +++ b/python/ql/test/query-tests/Expressions/super/CallToSuperWrongClass.qlref @@ -0,0 +1 @@ +Expressions/CallToSuperWrongClass.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/super/test.py b/python/ql/test/query-tests/Expressions/super/test.py new file mode 100644 index 000000000000..e2e667cd25d7 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/super/test.py @@ -0,0 +1,25 @@ +import sys + +#Container inheriting from builtin +class MyDict(dict): + pass + +class NotMyDict(object): + + def f(self): + super(MyDict, self).f() + +#Splitting +PY2 = sys.version_info[0] == 2 + +if PY2: + pass + + +class InSplit(MyDict): + + def __init__(self): + super(InSplit, self).f() + +if PY2: + pass diff --git a/python/ql/test/query-tests/Expressions/super/test_except.py b/python/ql/test/query-tests/Expressions/super/test_except.py new file mode 100644 index 000000000000..22935563a6e6 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/super/test_except.py @@ -0,0 +1,13 @@ + +try: + + @decorator + class S(object): + + def __init__(self, *args, **kwargs): + super(S, self).__init__(*args, **kwargs) + +except Exception: + + class S(object): + pass diff --git a/python/ql/test/query-tests/Functions/general/DeprecatedSliceMethod.expected b/python/ql/test/query-tests/Functions/general/DeprecatedSliceMethod.expected new file mode 100644 index 000000000000..8d89bff1f237 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/DeprecatedSliceMethod.expected @@ -0,0 +1,3 @@ +| functions_test.py:200:5:200:40 | Function __getslice__ | __getslice__ method has been deprecated since Python 2.0 | +| functions_test.py:203:5:203:47 | Function __setslice__ | __setslice__ method has been deprecated since Python 2.0 | +| functions_test.py:206:5:206:40 | Function __delslice__ | __delslice__ method has been deprecated since Python 2.0 | diff --git a/python/ql/test/query-tests/Functions/general/DeprecatedSliceMethod.qlref b/python/ql/test/query-tests/Functions/general/DeprecatedSliceMethod.qlref new file mode 100644 index 000000000000..c38b8d1f7619 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/DeprecatedSliceMethod.qlref @@ -0,0 +1 @@ +Functions/DeprecatedSliceMethod.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/general/ExplicitReturnInInit.expected b/python/ql/test/query-tests/Functions/general/ExplicitReturnInInit.expected new file mode 100644 index 000000000000..e695c7d00305 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/ExplicitReturnInInit.expected @@ -0,0 +1 @@ +| functions_test.py:45:9:45:19 | Return | Explicit return in __init__ method. | diff --git a/python/ql/test/query-tests/Functions/general/ExplicitReturnInInit.qlref b/python/ql/test/query-tests/Functions/general/ExplicitReturnInInit.qlref new file mode 100644 index 000000000000..a23550c48650 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/ExplicitReturnInInit.qlref @@ -0,0 +1 @@ +Functions/ExplicitReturnInInit.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.expected b/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.expected new file mode 100644 index 000000000000..a5049d58046b --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.expected @@ -0,0 +1,2 @@ +| protocols.py:66:5:66:33 | Function __getitem__ | Function always raises $@; raise LookupError instead | file://:Compiled Code:0:0:0:0 | builtin-class ZeroDivisionError | builtin-class ZeroDivisionError | +| protocols.py:69:5:69:26 | Function __getattr__ | Function always raises $@; raise AttributeError instead | file://:Compiled Code:0:0:0:0 | builtin-class ZeroDivisionError | builtin-class ZeroDivisionError | diff --git a/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.qlref b/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.qlref new file mode 100644 index 000000000000..07fd22a93767 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/IncorrectRaiseInSpecialMethod.qlref @@ -0,0 +1 @@ +Functions/IncorrectRaiseInSpecialMethod.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/general/InitIsGenerator.expected b/python/ql/test/query-tests/Functions/general/InitIsGenerator.expected new file mode 100644 index 000000000000..ec18b839c489 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/InitIsGenerator.expected @@ -0,0 +1 @@ +| functions_test.py:73:5:73:23 | Function __init__ | __init__ method is a generator. | diff --git a/python/ql/test/query-tests/Functions/general/InitIsGenerator.qlref b/python/ql/test/query-tests/Functions/general/InitIsGenerator.qlref new file mode 100644 index 000000000000..a3df140ff1e6 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/InitIsGenerator.qlref @@ -0,0 +1 @@ +Functions/InitIsGenerator.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/general/IterReturnsNonIterator.expected b/python/ql/test/query-tests/Functions/general/IterReturnsNonIterator.expected new file mode 100644 index 000000000000..0fbb62b7aa83 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/IterReturnsNonIterator.expected @@ -0,0 +1 @@ +| protocols.py:16:5:16:23 | Function __iter__ | The '__iter__' method of iterable class $@ does not return an iterator. | protocols.py:14:1:14:16 | class X | X | diff --git a/python/ql/test/query-tests/Functions/general/IterReturnsNonIterator.qlref b/python/ql/test/query-tests/Functions/general/IterReturnsNonIterator.qlref new file mode 100644 index 000000000000..3d0965f7b115 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/IterReturnsNonIterator.qlref @@ -0,0 +1 @@ +Functions/IterReturnsNonIterator.ql diff --git a/python/ql/test/query-tests/Functions/general/IterReturnsNonSelf.expected b/python/ql/test/query-tests/Functions/general/IterReturnsNonSelf.expected new file mode 100644 index 000000000000..88c2e672db8b --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/IterReturnsNonSelf.expected @@ -0,0 +1 @@ +| protocols.py:22:1:22:29 | class AlmostIterator | Class AlmostIterator is an iterator but its $@ method does not return 'self'. | protocols.py:30:5:30:23 | Function __iter__ | __iter__ | diff --git a/python/ql/test/query-tests/Functions/general/IterReturnsNonSelf.qlref b/python/ql/test/query-tests/Functions/general/IterReturnsNonSelf.qlref new file mode 100644 index 000000000000..b806215d26c8 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/IterReturnsNonSelf.qlref @@ -0,0 +1 @@ +Functions/IterReturnsNonSelf.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/general/ModificationOfParameterWithDefault.expected b/python/ql/test/query-tests/Functions/general/ModificationOfParameterWithDefault.expected new file mode 100644 index 000000000000..520b55ea3c27 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/ModificationOfParameterWithDefault.expected @@ -0,0 +1,2 @@ +| functions_test.py:40:5:40:17 | Attribute() | Modification of parameter $@, which has mutable default value. | functions_test.py:39:9:39:9 | Parameter | x | +| functions_test.py:239:5:239:14 | AugAssign | Modification of parameter $@, which has mutable default value. | functions_test.py:238:15:238:15 | Parameter | x | diff --git a/python/ql/test/query-tests/Functions/general/ModificationOfParameterWithDefault.qlref b/python/ql/test/query-tests/Functions/general/ModificationOfParameterWithDefault.qlref new file mode 100644 index 000000000000..8c4044e8feeb --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/ModificationOfParameterWithDefault.qlref @@ -0,0 +1 @@ +Functions/ModificationOfParameterWithDefault.ql diff --git a/python/ql/test/query-tests/Functions/general/NonCls.expected b/python/ql/test/query-tests/Functions/general/NonCls.expected new file mode 100644 index 000000000000..da955e6f353f --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/NonCls.expected @@ -0,0 +1,2 @@ +| functions_test.py:100:5:100:24 | Function n_cmethod | Class methods or methods of a type deriving from type should have 'cls', rather than 'self', as their first argument. | +| functions_test.py:114:5:114:20 | Function c_method | Class methods or methods of a type deriving from type should have 'cls', rather than 'y', as their first argument. | diff --git a/python/ql/test/query-tests/Functions/general/NonCls.qlref b/python/ql/test/query-tests/Functions/general/NonCls.qlref new file mode 100644 index 000000000000..b3525b101df2 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/NonCls.qlref @@ -0,0 +1 @@ +Functions/NonCls.ql diff --git a/python/ql/test/query-tests/Functions/general/NonSelf.expected b/python/ql/test/query-tests/Functions/general/NonSelf.expected new file mode 100644 index 000000000000..1bb0993593d6 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/NonSelf.expected @@ -0,0 +1,3 @@ +| functions_test.py:130:5:130:20 | Function __init__ | Normal methods should have 'self', rather than 'x', as their first parameter. | +| functions_test.py:133:5:133:20 | Function s_method | Normal methods should have 'self', rather than 'y', as their first parameter. | +| om_test.py:71:5:71:19 | Function __repr__ | Normal methods should have at least one parameter (the first of which should be 'self'). | diff --git a/python/ql/test/query-tests/Functions/general/NonSelf.qlref b/python/ql/test/query-tests/Functions/general/NonSelf.qlref new file mode 100644 index 000000000000..2629570c2a17 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/NonSelf.qlref @@ -0,0 +1 @@ +Functions/NonSelf.ql diff --git a/python/ql/test/query-tests/Functions/general/OverlyComplexDelMethod.expected b/python/ql/test/query-tests/Functions/general/OverlyComplexDelMethod.expected new file mode 100644 index 000000000000..9eb184f57bac --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/OverlyComplexDelMethod.expected @@ -0,0 +1 @@ +| protocols.py:42:5:42:22 | Function __del__ | Overly complex '__del__' method. | diff --git a/python/ql/test/query-tests/Functions/general/OverlyComplexDelMethod.qlref b/python/ql/test/query-tests/Functions/general/OverlyComplexDelMethod.qlref new file mode 100644 index 000000000000..601501aac303 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/OverlyComplexDelMethod.qlref @@ -0,0 +1 @@ +Functions/OverlyComplexDelMethod.ql diff --git a/python/ql/test/query-tests/Functions/general/SignatureOverriddenMethod.expected b/python/ql/test/query-tests/Functions/general/SignatureOverriddenMethod.expected new file mode 100644 index 000000000000..f23936e6a429 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/SignatureOverriddenMethod.expected @@ -0,0 +1,2 @@ +| om_test.py:32:5:32:35 | Function grossly_wrong1 | Overriding method 'grossly_wrong1' has signature mismatch with $@. | om_test.py:12:5:12:41 | Function grossly_wrong1 | overridden method | +| om_test.py:35:5:35:47 | Function grossly_wrong2 | Overriding method 'grossly_wrong2' has signature mismatch with $@. | om_test.py:15:5:15:41 | Function grossly_wrong2 | overridden method | diff --git a/python/ql/test/query-tests/Functions/general/SignatureOverriddenMethod.qlref b/python/ql/test/query-tests/Functions/general/SignatureOverriddenMethod.qlref new file mode 100644 index 000000000000..a306477b3b48 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/SignatureOverriddenMethod.qlref @@ -0,0 +1 @@ +Functions/SignatureOverriddenMethod.ql diff --git a/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.expected b/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.expected new file mode 100644 index 000000000000..d26ae70958e1 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.expected @@ -0,0 +1,9 @@ +| om_test.py:59:5:59:28 | Function __div__ | Too many parameters for special method __div__, which has 3 parameters, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:62:5:62:22 | Function __mul__ | Too few parameters for special method __mul__, which has 1 parameter, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:65:5:65:29 | Function __neg__ | Too many parameters for special method __neg__, which has 2 parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:68:5:68:35 | Function __exit__ | Too few parameters for special method __exit__, which has 3 parameters, but should have 4, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:71:5:71:19 | Function __repr__ | Too few parameters for special method __repr__, which has no parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:74:5:74:46 | Function __add__ | 1 default values(s) will never be used for special method __add__, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:97:15:97:34 | Function lambda | Too few parameters for special method __sub__, which has 1 parameter, but should have 2, in class $@. | om_test.py:95:1:95:28 | class NotOKSpecials | NotOKSpecials | +| protocols.py:72:1:72:12 | Function f | Too few parameters for special method __add__, which has 1 parameter, but should have 2, in class $@. | protocols.py:75:1:75:29 | class MissingMethods | MissingMethods | +| protocols.py:72:1:72:12 | Function f | Too few parameters for special method __set__, which has 1 parameter, but should have 3, in class $@. | protocols.py:75:1:75:29 | class MissingMethods | MissingMethods | diff --git a/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.qlref b/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.qlref new file mode 100644 index 000000000000..bc1b29b6c0d0 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.qlref @@ -0,0 +1 @@ +Functions/SignatureSpecialMethods.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/general/functions_test.py b/python/ql/test/query-tests/Functions/general/functions_test.py new file mode 100644 index 000000000000..71fea62a737a --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/functions_test.py @@ -0,0 +1,288 @@ + +#Consistent Returns + +__all__ = [ '' ] + +def ok1(x): + if x: + return None + else: + return + +def ok2(x): + if x: + return 4 + else: + return "Hi" + +def cr1(x): + if x: + return 4 + +def cr2(x): + if x: + return 4 + else: + return + +def ok3(x): + try: + return + finally: + do_something() + +#Modification of parameter with default + +def ok4(x = []): + return len(x) + +def mpd(x = []): + x.append("x") + +class ExplicitReturnInInit(object): + + def __init__(self): + return self + +#These are OK +class ExplicitReturnNoneInInit(object): + + def __init__(self): + return None + +class PlainReturnInInit(object): + + def __init__(self): + return + +def error(): + raise Exception() + +class InitCallsError(object): + + def __init__(self): + return error() + +class InitCallsInit(InitCallsError): + + def __init__(self): + return InitCallsError.__init__(self) + +class InitIsGenerator(object): + + def __init__(self): + yield self + +def use_implicit_return_value(arg): + x = do_nothing() + return call_non_callable(arg) + +#The return in the lambda is OK as it is auto-generated +y = lambda x : do_nothing() + +def do_nothing(): + pass + +#Using name other than 'cls' for first parameter in methods. +# This shouldn't apply to classmethods (first parameter should be 'cls' or similar) +# or static methods (first parameter can be anything) + +class Normal(object): + + def n_ok(self): + pass + + @staticmethod + def n_smethod(ok): + pass + + @classmethod + def n_cmethod(self): + pass + + # this is allowed because it has a decorator other than @classmethod + @classmethod + @id + def n_suppress(any_name): + pass + +class Class(type): + + def __init__(cls): + pass + + def c_method(y): + pass + + def c_ok(cls): + pass + + @id + def c_suppress(any_name): + pass + +#Using name other than 'self' for first parameter in methods. +# This shouldn't apply to classmethods (first parameter should be 'cls' or similar) +# or static methods (first parameter can be anything) + +class NonSelf(object): + + def __init__(x): + pass + + def s_method(y): + pass + + def s_ok(self): + pass + + @staticmethod + def s_smethod(ok): + pass + + @classmethod + def s_cmethod(cls): + pass + + def s_smethod2(ok): + pass + s_smethod2 = staticmethod(s_smethod2) + + def s_cmethod2(cls): + pass + s_cmethod2 = classmethod(s_cmethod2) + +def returns_self(self): + return self + +def return_value_ignored(): + ok2() + ok4() + sorted([1,2]) + +d = {} + +def use_return_values(): + x = ok2() + x = ok2() + x = ok3() + x = ok3() + x = ok4() + x = ok4() + x = y() + x = y() + x = returns_self() + x = returns_self() + x = sorted(x) + x = sorted(x) + x = ok2() + x = ok2() + x = ok3() + x = ok3() + x = ok4() + x = ok4() + x = y() + x = y() + x = returns_self() + x = returns_self() + x = sorted(x) + x = sorted(x) + +def ok_to_ignore(): + ok1 + do_nothing() + returns_self() + ok3() + y() + +class DeprecatedSliceMethods(object): + + def __getslice__(self, start, stop): + pass + + def __setslice__(self, start, stop, value): + pass + + def __delslice__(self, start, stop): + pass + + + +@decorator +def nested_call_implicit_return_func_ok(arg): + do_nothing() + + + + + + +#OK as it returns result of a call to super().__init__() +class InitCallsInit(InitCallsError): + + def __init__(self): + return super(InitCallsInit, self).__init__() + +#Harmless, so we allow it. +def use_implicit_return_value_ok(arg): + return do_nothing() + +def mutli_return(arg): + if arg: + return do_something() + else: + return do_nothing() + +#Modification of parameter with default + +def augassign(x = []): + x += ["x"] + + +#Possible FPs for non-self. ODASA-2439 + +class C(object): + def _func(f): + return f + + _func(x) + + #or + @_func + def meth(self): + pass + + +def dont_care(arg): + pass + +class C(object): + + meth = dont_care + +class Meta(type): + + #__new__ is an implicit class method, so the first arg is the metaclass + def __new__(metacls, name, bases, cls_dict): + return super(Meta, metacls).__new__(metacls, name, bases, cls_dict) + +#ODASA 3658 +from sys import exit +#Consistent returns +def ok5(): + try: + may_raise() + return 0 + except ValueError as e: + print(e) + exit(EXIT_ERROR) + +#ODASA-6062 +import zope.interface +class Z(zope.interface.Interface): + + def meth(arg): + pass + +Z().meth(0) + diff --git a/python/ql/test/query-tests/Functions/general/mox.py b/python/ql/test/query-tests/Functions/general/mox.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Functions/general/om_test.py b/python/ql/test/query-tests/Functions/general/om_test.py new file mode 100644 index 000000000000..cbee20625aa8 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/om_test.py @@ -0,0 +1,107 @@ + +#Signature overridden wrong + +class Base(object): + + def ok1(self, arg1, arg2 = 2): + return arg1, arg2 + + def ok2(self, arg1, arg2 = 2): + return arg1, arg2 + + def grossly_wrong1(self, arg1, arg2): + return arg1, arg2 + + def grossly_wrong2(self, arg1, arg2): + return arg1, arg2 + + def strictly_wrong1(self, arg1, arg2 = 2): + return arg1, arg2 + + def strictly_wrong2(self, arg1, arg2 = 2): + return arg1, arg2 + +class Derived(Base): + + def ok1(self, arg1, arg2 = 2): + return arg1, arg2 + + def ok2(self, arg1, arg2 = 2, arg3 = 3): + return arg1, arg2, arg3 + + def grossly_wrong1(self, arg1): + return arg1 + + def grossly_wrong2(self, arg1, arg2, arg3): + return arg1, arg2, arg3 + + def strictly_wrong1(self, arg1): + return arg1 + + def strictly_wrong2(self, arg1, arg2, arg3 = 3): + return arg1, arg2, arg3 + +#Special method signatures + +class Special(object): + + def __add__(self, x): + return self, x + + def __pos__(self): + return self + + def __str__(self): + return repr(self) + +class WrongSpecials(object): + + def __div__(self, x, y): + return self, x, y + + def __mul__(self): + return self + + def __neg__(self, other): + return self, other + + def __exit__(self, arg0, arg1): + return arg0 == arg1 + + def __repr__(): + pass + + def __add__(self, other="Unused default"): + pass + + @staticmethod + def __abs__(): + return 42 + +class OKSpecials(object): + + def __del__(): + state = some_state() + + def __del__(self): + use_the_state(state) + + return __del__ + + __del__ = __del__() + + __add__ = lambda x, y : do_add(x, y) + +class NotOKSpecials(object): + + __sub__ = lambda x : do_neg(x) + +#Correctly overridden builtin method +class LoggingDict(dict): + + def pop(self): + print("pop") + return dict.pop(self) + + + diff --git a/python/ql/test/query-tests/Functions/general/protocols.py b/python/ql/test/query-tests/Functions/general/protocols.py new file mode 100644 index 000000000000..739dda00e452 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/protocols.py @@ -0,0 +1,90 @@ + +class Iterator: + #Support both 2 and 3 protocol + + def __next__(self): + pass + + def next(self): + pass + + def __iter__(self): + return self + +class X(object): + + def __iter__(self): + return object() + + +#Iterator not returning self + +class AlmostIterator(object): + + def __next__(self): + pass + + def next(self): + pass + + def __iter__(self): + return X.Xiter(X()) + +class AlmostIterable(object): + + def __iter__(self): + return AlmostIterator() + +#Overly complex __del__ method + +class MegaDel(object): + + def __del__(self): + a = self.x + self.y + if a: + print(a) + if sys._getframe().f_lineno > 100: + print("Hello") + sum = 0 + for a in range(100): + sum += a + print(sum) + +class MiniDel(object): + + def close(self): + pass + + def __del__(self): + self.close() + +class IncorrectSpecialMethods(object): + + def __add__(self, other): + raise NotImplementedError() + + def __getitem__(self, index): + raise ZeroDivisionError() + + def __getattr__(self): + raise ZeroDivisionError() + +def f(self): + pass + +class MissingMethods(object): + + __repr__ = f # This should be OK + __add__ = f # But not this + __set__ = f # or this + +#OK Special method +class OK(object): + + def __call__(self): + yield 0 + raise StopIteration + + + + diff --git a/python/ql/test/query-tests/Functions/general/special.py b/python/ql/test/query-tests/Functions/general/special.py new file mode 100644 index 000000000000..e555e3923166 --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/special.py @@ -0,0 +1,15 @@ + +#Special +class C(object): + + def __init__(self): + pass + + def __enter__(self): + pass + + def __exit__(self, *args): + pass + + def __get__(self, *args): + pass \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/general/use_mox.py b/python/ql/test/query-tests/Functions/general/use_mox.py new file mode 100644 index 000000000000..cc150120504e --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/use_mox.py @@ -0,0 +1,11 @@ +import mox + +#Use mox +mox + +def f(): + pass + +#This may be OK as it might be mocked + +f().AndReturns("Something") diff --git a/python/ql/test/query-tests/Functions/general/zope/__init__.py b/python/ql/test/query-tests/Functions/general/zope/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Functions/general/zope/interface.py b/python/ql/test/query-tests/Functions/general/zope/interface.py new file mode 100644 index 000000000000..05def283197e --- /dev/null +++ b/python/ql/test/query-tests/Functions/general/zope/interface.py @@ -0,0 +1,6 @@ +#Fake zope.interface Module + +class InterfaceClass(type): + pass + +Interface = InterfaceClass() diff --git a/python/ql/test/query-tests/Functions/overriding/IncorrectlyOverriddenMethod.expected b/python/ql/test/query-tests/Functions/overriding/IncorrectlyOverriddenMethod.expected new file mode 100644 index 000000000000..92276ba22f0e --- /dev/null +++ b/python/ql/test/query-tests/Functions/overriding/IncorrectlyOverriddenMethod.expected @@ -0,0 +1,6 @@ +| test.py:24:5:24:26 | Function meth1 | Overriding method signature does not match $@, where it is passed too few. Overridden method $@ is correctly specified. | test.py:15:9:15:20 | Attribute() | here | test.py:5:5:5:20 | Function meth1 | method Base.meth1 | +| test.py:24:5:24:26 | Function meth1 | Overriding method signature does not match $@, where it is passed too few. Overridden method $@ is correctly specified. | test.py:34:9:34:20 | Attribute() | here | test.py:5:5:5:20 | Function meth1 | method Base.meth1 | +| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed an argument named 'spam'. Overridden method $@ is correctly specified. | test.py:20:9:20:31 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 | +| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed an argument named 'spam'. Overridden method $@ is correctly specified. | test.py:39:9:39:31 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 | +| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed too many. Overridden method $@ is correctly specified. | test.py:18:9:18:21 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 | +| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed too many. Overridden method $@ is correctly specified. | test.py:37:9:37:21 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 | diff --git a/python/ql/test/query-tests/Functions/overriding/IncorrectlyOverriddenMethod.qlref b/python/ql/test/query-tests/Functions/overriding/IncorrectlyOverriddenMethod.qlref new file mode 100644 index 000000000000..d1637c1f1d37 --- /dev/null +++ b/python/ql/test/query-tests/Functions/overriding/IncorrectlyOverriddenMethod.qlref @@ -0,0 +1 @@ +Functions/IncorrectlyOverriddenMethod.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/overriding/IncorrectlySpecifiedOverriddenMethod.expected b/python/ql/test/query-tests/Functions/overriding/IncorrectlySpecifiedOverriddenMethod.expected new file mode 100644 index 000000000000..527ad805604c --- /dev/null +++ b/python/ql/test/query-tests/Functions/overriding/IncorrectlySpecifiedOverriddenMethod.expected @@ -0,0 +1,9 @@ +| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed an argument named 'spam'. Overriding method $@ matches the call. | test.py:19:9:19:31 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 | +| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed an argument named 'spam'. Overriding method $@ matches the call. | test.py:38:9:38:31 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 | +| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:16:9:16:21 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 | +| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:19:9:19:31 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 | +| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:35:9:35:21 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 | +| test.py:5:5:5:20 | Function meth1 | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:38:9:38:31 | Attribute() | call | test.py:24:5:24:26 | Function meth1 | method Derived.meth1 | +| test.py:8:5:8:26 | Function meth2 | Overridden method signature does not match $@, where it is passed too few arguments. Overriding method $@ matches the call. | test.py:17:9:17:20 | Attribute() | call | test.py:27:5:27:20 | Function meth2 | method Derived.meth2 | +| test.py:8:5:8:26 | Function meth2 | Overridden method signature does not match $@, where it is passed too few arguments. Overriding method $@ matches the call. | test.py:36:9:36:20 | Attribute() | call | test.py:27:5:27:20 | Function meth2 | method Derived.meth2 | +| test.py:64:5:64:19 | Function meth | Overridden method signature does not match $@, where it is passed too many arguments. Overriding method $@ matches the call. | test.py:78:1:78:12 | Attribute() | call | test.py:74:5:74:24 | Function meth | method Correct2.meth | diff --git a/python/ql/test/query-tests/Functions/overriding/IncorrectlySpecifiedOverriddenMethod.qlref b/python/ql/test/query-tests/Functions/overriding/IncorrectlySpecifiedOverriddenMethod.qlref new file mode 100644 index 000000000000..8a07cb1297ea --- /dev/null +++ b/python/ql/test/query-tests/Functions/overriding/IncorrectlySpecifiedOverriddenMethod.qlref @@ -0,0 +1 @@ +Functions/IncorrectlySpecifiedOverriddenMethod.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/overriding/SignatureOverriddenMethod.expected b/python/ql/test/query-tests/Functions/overriding/SignatureOverriddenMethod.expected new file mode 100644 index 000000000000..99f0fb667d6a --- /dev/null +++ b/python/ql/test/query-tests/Functions/overriding/SignatureOverriddenMethod.expected @@ -0,0 +1 @@ +| test.py:30:5:30:26 | Function meth3 | Overriding method 'meth3' has signature mismatch with $@. | test.py:11:5:11:20 | Function meth3 | overridden method | diff --git a/python/ql/test/query-tests/Functions/overriding/SignatureOverriddenMethod.qlref b/python/ql/test/query-tests/Functions/overriding/SignatureOverriddenMethod.qlref new file mode 100644 index 000000000000..a306477b3b48 --- /dev/null +++ b/python/ql/test/query-tests/Functions/overriding/SignatureOverriddenMethod.qlref @@ -0,0 +1 @@ +Functions/SignatureOverriddenMethod.ql diff --git a/python/ql/test/query-tests/Functions/overriding/WrongNameForArgumentInCall.expected b/python/ql/test/query-tests/Functions/overriding/WrongNameForArgumentInCall.expected new file mode 100644 index 000000000000..d2fc2ef6784f --- /dev/null +++ b/python/ql/test/query-tests/Functions/overriding/WrongNameForArgumentInCall.expected @@ -0,0 +1 @@ +| test.py:19:9:19:31 | Attribute() | Keyword argument 'spam' is not a supported parameter name of $@. | test.py:5:5:5:20 | Function meth1 | method Base.meth1 | diff --git a/python/ql/test/query-tests/Functions/overriding/WrongNameForArgumentInCall.qlref b/python/ql/test/query-tests/Functions/overriding/WrongNameForArgumentInCall.qlref new file mode 100644 index 000000000000..3599f204f55e --- /dev/null +++ b/python/ql/test/query-tests/Functions/overriding/WrongNameForArgumentInCall.qlref @@ -0,0 +1 @@ +Expressions/WrongNameForArgumentInCall.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/overriding/WrongNumberArgumentsInCall.expected b/python/ql/test/query-tests/Functions/overriding/WrongNumberArgumentsInCall.expected new file mode 100644 index 000000000000..4f29401ce5be --- /dev/null +++ b/python/ql/test/query-tests/Functions/overriding/WrongNumberArgumentsInCall.expected @@ -0,0 +1,2 @@ +| test.py:16:9:16:21 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | test.py:5:5:5:20 | Function meth1 | method Base.meth1 | +| test.py:17:9:17:20 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | test.py:8:5:8:26 | Function meth2 | method Base.meth2 | diff --git a/python/ql/test/query-tests/Functions/overriding/WrongNumberArgumentsInCall.qlref b/python/ql/test/query-tests/Functions/overriding/WrongNumberArgumentsInCall.qlref new file mode 100644 index 000000000000..1bffe8f1cad4 --- /dev/null +++ b/python/ql/test/query-tests/Functions/overriding/WrongNumberArgumentsInCall.qlref @@ -0,0 +1 @@ +Expressions/WrongNumberArgumentsInCall.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/overriding/test.py b/python/ql/test/query-tests/Functions/overriding/test.py new file mode 100644 index 000000000000..c4c7caaa1aad --- /dev/null +++ b/python/ql/test/query-tests/Functions/overriding/test.py @@ -0,0 +1,78 @@ + + +class Base(object): + + def meth1(self): + pass + + def meth2(self, spam): + pass + + def meth3(self): + pass + + def foo(self): + self.meth1() + self.meth1(0) + self.meth2() + self.meth2(0) + self.meth1(spam="eggs") + self.meth2(spam="eggs") + +class Derived(Base): + + def meth1(self, spam): + pass + + def meth2(self): + pass + + def meth3(self, eggs): #Incorrectly overridden and not called. + pass + + def bar(self): + self.meth1() # Can only call Derived.meth1 so report as incorrect number of arguments + self.meth1(0) + self.meth2() + self.meth2(0) # Can only call Derived.meth2 so report as incorrect number of arguments + self.meth1(spam="eggs") + self.meth2(spam="eggs") + self.foo() + +d = Derived() +d.meth1 + +class Abstract(object): + + def meth(self): + raise NotImplementedError() + + +class Concrete(object): + + def meth(self, arg): + pass + + +#Attempt to fool analysis +x = Abstract() if cond else Concrete() +x.meth("hi") + + +class BlameBase(object): + + def meth(self): + pass + +class Correct1(BlameBase): + + def meth(self, arg): + pass + +class Correct2(BlameBase): + + def meth(self, arg): + pass + +c = Correct2() +c.meth("hi") diff --git a/python/ql/test/query-tests/Functions/return_values/ConsistentReturns.expected b/python/ql/test/query-tests/Functions/return_values/ConsistentReturns.expected new file mode 100644 index 000000000000..7c0bad373b37 --- /dev/null +++ b/python/ql/test/query-tests/Functions/return_values/ConsistentReturns.expected @@ -0,0 +1,2 @@ +| functions_test.py:18:1:18:11 | Function cr1 | Mixing implicit and explicit returns may indicate an error as implicit returns always return None. | +| functions_test.py:22:1:22:11 | Function cr2 | Mixing implicit and explicit returns may indicate an error as implicit returns always return None. | diff --git a/python/ql/test/query-tests/Functions/return_values/ConsistentReturns.qlref b/python/ql/test/query-tests/Functions/return_values/ConsistentReturns.qlref new file mode 100644 index 000000000000..0904074f25b4 --- /dev/null +++ b/python/ql/test/query-tests/Functions/return_values/ConsistentReturns.qlref @@ -0,0 +1 @@ +Functions/ConsistentReturns.ql diff --git a/python/ql/test/query-tests/Functions/return_values/ReturnConsistentTupleSizes.expected b/python/ql/test/query-tests/Functions/return_values/ReturnConsistentTupleSizes.expected new file mode 100644 index 000000000000..fd4f1ee2dd70 --- /dev/null +++ b/python/ql/test/query-tests/Functions/return_values/ReturnConsistentTupleSizes.expected @@ -0,0 +1,2 @@ +| functions_test.py:306:1:306:39 | Function returning_different_tuple_sizes | returning_different_tuple_sizes returns $@ and $@. | functions_test.py:308:16:308:18 | Tuple | tuple of size 2 | functions_test.py:310:16:310:20 | Tuple | tuple of size 3 | +| functions_test.py:324:1:324:50 | Function indirectly_returning_different_tuple_sizes | indirectly_returning_different_tuple_sizes returns $@ and $@. | functions_test.py:319:12:319:14 | Tuple | tuple of size 2 | functions_test.py:322:12:322:16 | Tuple | tuple of size 3 | diff --git a/python/ql/test/query-tests/Functions/return_values/ReturnConsistentTupleSizes.qlref b/python/ql/test/query-tests/Functions/return_values/ReturnConsistentTupleSizes.qlref new file mode 100644 index 000000000000..c91661b33cf4 --- /dev/null +++ b/python/ql/test/query-tests/Functions/return_values/ReturnConsistentTupleSizes.qlref @@ -0,0 +1 @@ +Functions/ReturnConsistentTupleSizes.ql diff --git a/python/ql/test/query-tests/Functions/return_values/ReturnValueIgnored.expected b/python/ql/test/query-tests/Functions/return_values/ReturnValueIgnored.expected new file mode 100644 index 000000000000..60640bb96ba2 --- /dev/null +++ b/python/ql/test/query-tests/Functions/return_values/ReturnValueIgnored.expected @@ -0,0 +1,3 @@ +| functions_test.py:159:5:159:9 | ExprStmt | Call discards return value of function $@. The result is used in 80% of calls. | functions_test.py:12:1:12:11 | Function ok2 | ok2 | +| functions_test.py:160:5:160:9 | ExprStmt | Call discards return value of function $@. The result is used in 80% of calls. | functions_test.py:36:1:36:11 | Function ok4 | ok4 | +| functions_test.py:161:5:161:17 | ExprStmt | Call discards return value of function $@. The result is used in 80% of calls. | file://:Compiled Code:0:0:0:0 | Builtin-function sorted | sorted | diff --git a/python/ql/test/query-tests/Functions/return_values/ReturnValueIgnored.qlref b/python/ql/test/query-tests/Functions/return_values/ReturnValueIgnored.qlref new file mode 100644 index 000000000000..61002533ef49 --- /dev/null +++ b/python/ql/test/query-tests/Functions/return_values/ReturnValueIgnored.qlref @@ -0,0 +1 @@ +Functions/ReturnValueIgnored.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/return_values/UseImplicitNoneReturnValue.expected b/python/ql/test/query-tests/Functions/return_values/UseImplicitNoneReturnValue.expected new file mode 100644 index 000000000000..54a74971d37b --- /dev/null +++ b/python/ql/test/query-tests/Functions/return_values/UseImplicitNoneReturnValue.expected @@ -0,0 +1,2 @@ +| functions_test.py:77:9:77:20 | do_nothing() | The result of '$@' is used even though it is always None. | functions_test.py:83:1:83:17 | Function do_nothing | do_nothing | +| functions_test.py:234:16:234:27 | do_nothing() | The result of '$@' is used even though it is always None. | functions_test.py:83:1:83:17 | Function do_nothing | do_nothing | diff --git a/python/ql/test/query-tests/Functions/return_values/UseImplicitNoneReturnValue.qlref b/python/ql/test/query-tests/Functions/return_values/UseImplicitNoneReturnValue.qlref new file mode 100644 index 000000000000..b23115e8950c --- /dev/null +++ b/python/ql/test/query-tests/Functions/return_values/UseImplicitNoneReturnValue.qlref @@ -0,0 +1 @@ +Functions/UseImplicitNoneReturnValue.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Functions/return_values/functions_test.py b/python/ql/test/query-tests/Functions/return_values/functions_test.py new file mode 100644 index 000000000000..c8a7cf377348 --- /dev/null +++ b/python/ql/test/query-tests/Functions/return_values/functions_test.py @@ -0,0 +1,333 @@ + +#Consistent Returns + +__all__ = [ '' ] + +def ok1(x): + if x: + return None + else: + return + +def ok2(x): + if x: + return 4 + else: + return "Hi" + +def cr1(x): + if x: + return 4 + +def cr2(x): + if x: + return 4 + else: + return + +def ok3(x): + try: + return + finally: + do_something() + + + +def ok4(x): + return len(x) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +def use_implicit_return_value(arg): + x = do_nothing() + return call_non_callable(arg) + +#The return in the lambda is OK as it is auto-generated +y = lambda x : do_nothing() + +def do_nothing(): + pass + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +def return_value_ignored(): + ok2() + ok4() + sorted([1,2]) + +d = {} + +def use_return_values(): + x = ok2() + x = ok2() + x = ok3() + x = ok3() + x = ok4() + x = ok4() + x = y() + x = y() + x = returns_self() + x = returns_self() + x = sorted(x) + x = sorted(x) + x = ok2() + x = ok2() + x = ok3() + x = ok3() + x = ok4() + x = ok4() + x = y() + x = y() + x = returns_self() + x = returns_self() + x = sorted(x) + x = sorted(x) + +def ok_to_ignore(): + ok1 + do_nothing() + returns_self() + ok3() + y() + + + + + + + + + + + + + + +@decorator +def nested_call_implicit_return_func_ok(arg): + do_nothing() + + + + + + +#OK as it returns result of a call to super().__init__() +class InitCallsInit(InitCallsError): + + def __init__(self): + return super(InitCallsInit, self).__init__() + +#Harmless, so we allow it. +def use_implicit_return_value_ok(arg): + return do_nothing() + +def mutli_return(arg): + if arg: + return do_something() + else: + return do_nothing() + +#Modification of parameter with default + +def augassign(x = []): + x += ["x"] + + +#Possible FPs for non-self. ODASA-2439 + +class C(object): + def _func(f): + return f + + _func(x) + + #or + @_func + def meth(self): + pass + + +def dont_care(arg): + pass + +class C(object): + + meth = dont_care + +class Meta(type): + + #__new__ is an implicit class method, so the first arg is the metaclass + def __new__(metacls, name, bases, cls_dict): + return super(Meta, metacls).__new__(metacls, name, bases, cls_dict) + +#ODASA 3658 +from sys import exit +#Consistent returns +def ok5(): + try: + may_raise() + return 0 + except ValueError as e: + print(e) + exit(EXIT_ERROR) + + + +#ODASA-6514 +if unknown(): + def foo(x): + pass +else: + def foo(x): + return x+1 + +#This is OK since at least one of the `foo`s returns a value. +y = foo() + + + + + + + + + + + + +# Returning tuples with different sizes + +def returning_different_tuple_sizes(x): + if x: + return 1,2 + else: + return 1,2,3 + +def ok_tuple_sizes(x): + if x: + return 1,2 + else: + return 2,3 + +def function_returning_2_tuple(): + return 1,2 + +def function_returning_3_tuple(): + return 1,2,3 + +def indirectly_returning_different_tuple_sizes(x): + if x: + return function_returning_2_tuple() + else: + return function_returning_3_tuple() + + +def mismatched_multi_assign(x): + a,b = returning_different_tuple_sizes(x) + return a,b \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/PyCheckerTests/ImportandImportFrom.expected b/python/ql/test/query-tests/Imports/PyCheckerTests/ImportandImportFrom.expected new file mode 100644 index 000000000000..ca3e04150e2e --- /dev/null +++ b/python/ql/test/query-tests/Imports/PyCheckerTests/ImportandImportFrom.expected @@ -0,0 +1 @@ +| imports_test.py:4:1:4:19 | Import | Module 'test_module2' is imported with both 'import' and 'import from' | diff --git a/python/ql/test/query-tests/Imports/PyCheckerTests/ImportandImportFrom.qlref b/python/ql/test/query-tests/Imports/PyCheckerTests/ImportandImportFrom.qlref new file mode 100644 index 000000000000..3d50843db7eb --- /dev/null +++ b/python/ql/test/query-tests/Imports/PyCheckerTests/ImportandImportFrom.qlref @@ -0,0 +1 @@ +Imports/ImportandImportFrom.ql diff --git a/python/ql/test/query-tests/Imports/PyCheckerTests/ModuleImportsItself.expected b/python/ql/test/query-tests/Imports/PyCheckerTests/ModuleImportsItself.expected new file mode 100644 index 000000000000..497c729b8ad3 --- /dev/null +++ b/python/ql/test/query-tests/Imports/PyCheckerTests/ModuleImportsItself.expected @@ -0,0 +1 @@ +| imports_test.py:8:1:8:19 | Import | The module 'imports_test' imports itself. | diff --git a/python/ql/test/query-tests/Imports/PyCheckerTests/ModuleImportsItself.qlref b/python/ql/test/query-tests/Imports/PyCheckerTests/ModuleImportsItself.qlref new file mode 100644 index 000000000000..e6bc27b30652 --- /dev/null +++ b/python/ql/test/query-tests/Imports/PyCheckerTests/ModuleImportsItself.qlref @@ -0,0 +1 @@ +Imports/ModuleImportsItself.ql diff --git a/python/ql/test/query-tests/Imports/PyCheckerTests/imports_test.py b/python/ql/test/query-tests/Imports/PyCheckerTests/imports_test.py new file mode 100644 index 000000000000..81ebbccd9d5a --- /dev/null +++ b/python/ql/test/query-tests/Imports/PyCheckerTests/imports_test.py @@ -0,0 +1,9 @@ + +#Import and import from + +import test_module2 +from test_module2 import func + +#Module imports itself +import imports_test + diff --git a/python/ql/test/query-tests/Imports/PyCheckerTests/test_module.py b/python/ql/test/query-tests/Imports/PyCheckerTests/test_module.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/ql/test/query-tests/Imports/PyCheckerTests/test_module.py @@ -0,0 +1 @@ + diff --git a/python/ql/test/query-tests/Imports/PyCheckerTests/test_module2.py b/python/ql/test/query-tests/Imports/PyCheckerTests/test_module2.py new file mode 100644 index 000000000000..94e8b95ac1f5 --- /dev/null +++ b/python/ql/test/query-tests/Imports/PyCheckerTests/test_module2.py @@ -0,0 +1,4 @@ + +def func(): + pass + diff --git a/python/ql/test/query-tests/Imports/cyclic-module/CyclicImport.expected b/python/ql/test/query-tests/Imports/cyclic-module/CyclicImport.expected new file mode 100644 index 000000000000..e5449848fdcb --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/CyclicImport.expected @@ -0,0 +1,11 @@ +| module1.py:3:1:3:14 | Import | Import of module $@ begins an import cycle. | module3.py:0:0:0:0 | Module module3 | module3 | +| module1.py:9:1:9:14 | Import | Import of module $@ begins an import cycle. | module4.py:0:0:0:0 | Module module4 | module4 | +| module1.py:11:5:11:18 | Import | Import of module $@ begins an import cycle. | module5.py:0:0:0:0 | Module module5 | module5 | +| module1.py:14:1:14:14 | Import | Import of module $@ begins an import cycle. | module6.py:0:0:0:0 | Module module6 | module6 | +| module1.py:17:1:17:14 | Import | Import of module $@ begins an import cycle. | module8.py:0:0:0:0 | Module module8 | module8 | +| module4.py:1:1:1:14 | Import | Import of module $@ begins an import cycle. | module1.py:0:0:0:0 | Module module1 | module1 | +| module5.py:1:1:1:14 | Import | Import of module $@ begins an import cycle. | module1.py:0:0:0:0 | Module module1 | module1 | +| module6.py:2:5:2:18 | Import | Import of module $@ begins an import cycle. | module7.py:0:0:0:0 | Module module7 | module7 | +| module7.py:1:1:1:22 | Import | Import of module $@ begins an import cycle. | module1.py:0:0:0:0 | Module module1 | module1 | +| module8.py:1:1:1:14 | Import | Import of module $@ begins an import cycle. | module1.py:0:0:0:0 | Module module1 | module1 | +| module9.py:4:1:4:11 | Import | Import of module $@ begins an import cycle. | main.py:0:0:0:0 | Module main | main | diff --git a/python/ql/test/query-tests/Imports/cyclic-module/CyclicImport.qlref b/python/ql/test/query-tests/Imports/cyclic-module/CyclicImport.qlref new file mode 100644 index 000000000000..814bba9fad6a --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/CyclicImport.qlref @@ -0,0 +1 @@ +Imports/CyclicImport.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/cyclic-module/ModuleLevelCyclicImport.expected b/python/ql/test/query-tests/Imports/cyclic-module/ModuleLevelCyclicImport.expected new file mode 100644 index 000000000000..dbdc671c3850 --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/ModuleLevelCyclicImport.expected @@ -0,0 +1,3 @@ +| module1.py:5:6:5:15 | Attribute | 'a2' may not be defined if module $@ is imported before module $@, as the $@ of a2 occurs after the cyclic $@ of module1. | module2.py:0:0:0:0 | Module module2 | module2 | module1.py:0:0:0:0 | Module module1 | module1 | module2.py:4:1:4:2 | ControlFlowNode for a2 | definition | module2.py:1:1:1:14 | Import | import | +| module2.py:4:6:4:15 | Attribute | 'a1' may not be defined if module $@ is imported before module $@, as the $@ of a1 occurs after the cyclic $@ of module2. | module1.py:0:0:0:0 | Module module1 | module1 | module2.py:0:0:0:0 | Module module2 | module2 | module1.py:5:1:5:2 | ControlFlowNode for a1 | definition | module1.py:2:1:2:14 | Import | import | +| module3.py:2:21:2:22 | ImportMember | 'a1' may not be defined if module $@ is imported before module $@, as the $@ of a1 occurs after the cyclic $@ of module3. | module1.py:0:0:0:0 | Module module1 | module1 | module3.py:0:0:0:0 | Module module3 | module3 | module1.py:5:1:5:2 | ControlFlowNode for a1 | definition | module1.py:3:1:3:14 | Import | import | diff --git a/python/ql/test/query-tests/Imports/cyclic-module/ModuleLevelCyclicImport.qlref b/python/ql/test/query-tests/Imports/cyclic-module/ModuleLevelCyclicImport.qlref new file mode 100644 index 000000000000..5119f8fdaae2 --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/ModuleLevelCyclicImport.qlref @@ -0,0 +1 @@ +Imports/ModuleLevelCyclicImport.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/cyclic-module/main.py b/python/ql/test/query-tests/Imports/cyclic-module/main.py new file mode 100644 index 000000000000..8da504e0406f --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/main.py @@ -0,0 +1,7 @@ + +a = 1 +b = 2 + +if __name__ == '__main__': + import module9 + print(module9.y) diff --git a/python/ql/test/query-tests/Imports/cyclic-module/module1.py b/python/ql/test/query-tests/Imports/cyclic-module/module1.py new file mode 100644 index 000000000000..55ef075e9e6e --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/module1.py @@ -0,0 +1,22 @@ +# potentially crashing cycles +import module2 +import module3 + +a1 = module2.a2 +b1 = 2 + +# bad style cycles +import module4 +def foo(): + import module5 + +# okay, because some of the cycle is not top level +import module6 + +# OK because this import occurs after relevant definition (a1) +import module8 + +#OK because cycle is guarded by `if False:` +from module10 import x + + diff --git a/python/ql/test/query-tests/Imports/cyclic-module/module10.py b/python/ql/test/query-tests/Imports/cyclic-module/module10.py new file mode 100644 index 000000000000..68127cc21fda --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/module10.py @@ -0,0 +1,5 @@ + +if False: + import module1 + +x = 1 diff --git a/python/ql/test/query-tests/Imports/cyclic-module/module2.py b/python/ql/test/query-tests/Imports/cyclic-module/module2.py new file mode 100644 index 000000000000..333b8516adb3 --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/module2.py @@ -0,0 +1,4 @@ +import module1 + +# direct use +a2 = module1.a1 \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/cyclic-module/module3.py b/python/ql/test/query-tests/Imports/cyclic-module/module3.py new file mode 100644 index 000000000000..2180fb54a285 --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/module3.py @@ -0,0 +1,2 @@ +# use via import member +from module1 import a1 \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/cyclic-module/module4.py b/python/ql/test/query-tests/Imports/cyclic-module/module4.py new file mode 100644 index 000000000000..65db406bb451 --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/module4.py @@ -0,0 +1 @@ +import module1 \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/cyclic-module/module5.py b/python/ql/test/query-tests/Imports/cyclic-module/module5.py new file mode 100644 index 000000000000..65db406bb451 --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/module5.py @@ -0,0 +1 @@ +import module1 \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/cyclic-module/module6.py b/python/ql/test/query-tests/Imports/cyclic-module/module6.py new file mode 100644 index 000000000000..5a5fcd149ac6 --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/module6.py @@ -0,0 +1,2 @@ +def foo(): + import module7 \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/cyclic-module/module7.py b/python/ql/test/query-tests/Imports/cyclic-module/module7.py new file mode 100644 index 000000000000..d0b18ba5894d --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/module7.py @@ -0,0 +1 @@ +from module1 import a1 \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/cyclic-module/module8.py b/python/ql/test/query-tests/Imports/cyclic-module/module8.py new file mode 100644 index 000000000000..185ee214da83 --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/module8.py @@ -0,0 +1,4 @@ +import module1 + +class Foo(object): + a = module1.a1 \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/cyclic-module/module9.py b/python/ql/test/query-tests/Imports/cyclic-module/module9.py new file mode 100644 index 000000000000..06ab5b933058 --- /dev/null +++ b/python/ql/test/query-tests/Imports/cyclic-module/module9.py @@ -0,0 +1,6 @@ + +x = 1 + +import main + +y = 2 \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/deprecated/DeprecatedModule.expected b/python/ql/test/query-tests/Imports/deprecated/DeprecatedModule.expected new file mode 100644 index 000000000000..11905b13d9cd --- /dev/null +++ b/python/ql/test/query-tests/Imports/deprecated/DeprecatedModule.expected @@ -0,0 +1,2 @@ +| test.py:5:1:5:13 | Import | The rfc822 module was deprecated in version 2.3. Use email module instead. | +| test.py:6:1:6:16 | Import | The posixfile module was deprecated in version 1.5. Use email module instead. | diff --git a/python/ql/test/query-tests/Imports/deprecated/DeprecatedModule.qlref b/python/ql/test/query-tests/Imports/deprecated/DeprecatedModule.qlref new file mode 100644 index 000000000000..3444aa9d6f69 --- /dev/null +++ b/python/ql/test/query-tests/Imports/deprecated/DeprecatedModule.qlref @@ -0,0 +1 @@ +Imports/DeprecatedModule.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/deprecated/test.py b/python/ql/test/query-tests/Imports/deprecated/test.py new file mode 100644 index 000000000000..ece57bb1855c --- /dev/null +++ b/python/ql/test/query-tests/Imports/deprecated/test.py @@ -0,0 +1,6 @@ + + + +#Some deprecated modules +import rfc822 +import posixfile diff --git a/python/ql/test/query-tests/Imports/general/FromImportOfMutableAttribute.expected b/python/ql/test/query-tests/Imports/general/FromImportOfMutableAttribute.expected new file mode 100644 index 000000000000..aa785201f246 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/FromImportOfMutableAttribute.expected @@ -0,0 +1 @@ +| imports_mutable.py:1:26:1:26 | ImportMember | Importing the value of 'x' from $@ means that any change made to $@ will be not be observed locally. | mutable_attr.py:0:0:0:0 | Module mutable_attr | module mutable_attr | mutates.py:4:5:4:18 | ControlFlowNode for Attribute | mutable_attr.x | diff --git a/python/ql/test/query-tests/Imports/general/FromImportOfMutableAttribute.qlref b/python/ql/test/query-tests/Imports/general/FromImportOfMutableAttribute.qlref new file mode 100644 index 000000000000..9353115309f8 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/FromImportOfMutableAttribute.qlref @@ -0,0 +1 @@ +Imports/FromImportOfMutableAttribute.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/general/ImportShadowedByLoopVar.expected b/python/ql/test/query-tests/Imports/general/ImportShadowedByLoopVar.expected new file mode 100644 index 000000000000..562cc12c51e2 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/ImportShadowedByLoopVar.expected @@ -0,0 +1 @@ +| imports_test.py:16:5:16:10 | module | Loop variable 'module' shadows an import | diff --git a/python/ql/test/query-tests/Imports/general/ImportShadowedByLoopVar.qlref b/python/ql/test/query-tests/Imports/general/ImportShadowedByLoopVar.qlref new file mode 100644 index 000000000000..3844f21922fb --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/ImportShadowedByLoopVar.qlref @@ -0,0 +1 @@ +Imports/ImportShadowedByLoopVar.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/general/ImportStarUsed.expected b/python/ql/test/query-tests/Imports/general/ImportStarUsed.expected new file mode 100644 index 000000000000..9aa101edbca6 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/ImportStarUsed.expected @@ -0,0 +1,2 @@ +| imports_test.py:21:1:21:20 | from module import * | Using 'from ... import *' pollutes the namespace | +| imports_test.py:22:1:22:32 | from module_without_all import * | Using 'from ... import *' pollutes the namespace | diff --git a/python/ql/test/query-tests/Imports/general/ImportStarUsed.qlref b/python/ql/test/query-tests/Imports/general/ImportStarUsed.qlref new file mode 100644 index 000000000000..35f8bff3e5fc --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/ImportStarUsed.qlref @@ -0,0 +1 @@ +Imports/ImportStarUsed.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/general/Imports.expected b/python/ql/test/query-tests/Imports/general/Imports.expected new file mode 100644 index 000000000000..59f5437fea36 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/Imports.expected @@ -0,0 +1 @@ +| imports_test.py:2:1:2:23 | Import | Multiple imports on one line. | diff --git a/python/ql/test/query-tests/Imports/general/Imports.qlref b/python/ql/test/query-tests/Imports/general/Imports.qlref new file mode 100644 index 000000000000..6bcdb2d9b5fd --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/Imports.qlref @@ -0,0 +1 @@ +Imports/Imports.ql diff --git a/python/ql/test/query-tests/Imports/general/MultipleImport.expected b/python/ql/test/query-tests/Imports/general/MultipleImport.expected new file mode 100644 index 000000000000..6e3938a67f58 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/MultipleImport.expected @@ -0,0 +1,2 @@ +| imports_test.py:33:1:33:14 | Import | This import of module module1 is redundant, as it was previously imported $@. | imports_test.py:2:1:2:23 | Import | on line 2 | +| imports_test.py:34:1:34:14 | Import | This import of module module2 is redundant, as it was previously imported $@. | imports_test.py:2:1:2:23 | Import | on line 2 | diff --git a/python/ql/test/query-tests/Imports/general/MultipleImport.qlref b/python/ql/test/query-tests/Imports/general/MultipleImport.qlref new file mode 100644 index 000000000000..a4d2195b6886 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/MultipleImport.qlref @@ -0,0 +1 @@ +Imports/MultipleImports.ql diff --git a/python/ql/test/query-tests/Imports/general/UnintentionalImport.expected b/python/ql/test/query-tests/Imports/general/UnintentionalImport.expected new file mode 100644 index 000000000000..89e48e2f0712 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/UnintentionalImport.expected @@ -0,0 +1 @@ +| imports_test.py:22:1:22:32 | from module_without_all import * | Import pollutes the enclosing namespace, as the imported module $@ does not define '__all__'. | module_without_all.py:0:0:0:0 | Module module_without_all | module_without_all | diff --git a/python/ql/test/query-tests/Imports/general/UnintentionalImport.qlref b/python/ql/test/query-tests/Imports/general/UnintentionalImport.qlref new file mode 100644 index 000000000000..4f1b985d5c2c --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/UnintentionalImport.qlref @@ -0,0 +1 @@ +Imports/UnintentionalImport.ql diff --git a/python/ql/test/query-tests/Imports/general/imports_mutable.py b/python/ql/test/query-tests/Imports/general/imports_mutable.py new file mode 100644 index 000000000000..0519a2071d8a --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/imports_mutable.py @@ -0,0 +1,5 @@ +from mutable_attr import x, y + +def f(): + print(x) + print(y) diff --git a/python/ql/test/query-tests/Imports/general/imports_test.py b/python/ql/test/query-tests/Imports/general/imports_test.py new file mode 100644 index 000000000000..5e5184b78c7e --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/imports_test.py @@ -0,0 +1,63 @@ +#Multiple imports on a single line +import module1, module2 + +#Cyclic import + +import cycle + +#Top level cyclic import + +import top_level_cycle + +#Import shadowed by loop variable + +import module + +for module in range(10): + print(module) + +#Import * used + +from module import * +from module_without_all import * + +#Unused import + +from module2 import func1 +from module2 import func2 + +module1.func +func1 + +#Duplicate import +import module1 +import module2 + +#OK -- Import used in epytext documentation. +import used_in_docs + +# L{used_in_docs} + +#OK -- Used in class. +import used_in_class + +class C(object): + + used_in_class = used_in_class + +#OK unused imports -- ODASA-3978 +from module2 import func3 as _ +from module2 import func3 as unused_but_ok +from module2 import func3 as _________ + +class Foo(object): + from bar import baz + def __init__(self): + self.foo = self.baz() + +#OK duplicate import -- Different name +import module1 as different + +#Use it +different + diff --git a/python/ql/test/query-tests/Imports/general/module.py b/python/ql/test/query-tests/Imports/general/module.py new file mode 100644 index 000000000000..7004607618aa --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/module.py @@ -0,0 +1,4 @@ +#Ignore from __future__ imports when considering unused imports +from __future__ import division, print_function + +__all__ = [] diff --git a/python/ql/test/query-tests/Imports/general/module1.py b/python/ql/test/query-tests/Imports/general/module1.py new file mode 100644 index 000000000000..b9bfa6f12339 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/module1.py @@ -0,0 +1,2 @@ +def func(): + pass diff --git a/python/ql/test/query-tests/Imports/general/module2.py b/python/ql/test/query-tests/Imports/general/module2.py new file mode 100644 index 000000000000..4b0947ab6b77 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/module2.py @@ -0,0 +1,8 @@ +def func1(): + pass + +def func2(): + pass + +def func3(): + pass \ No newline at end of file diff --git a/python/ql/test/query-tests/Imports/general/module_without_all.py b/python/ql/test/query-tests/Imports/general/module_without_all.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Imports/general/mutable_attr.py b/python/ql/test/query-tests/Imports/general/mutable_attr.py new file mode 100644 index 000000000000..2c1854573a40 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/mutable_attr.py @@ -0,0 +1,2 @@ +x = 1 +y = 2 diff --git a/python/ql/test/query-tests/Imports/general/mutates.py b/python/ql/test/query-tests/Imports/general/mutates.py new file mode 100644 index 000000000000..5a99a43b7ada --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/mutates.py @@ -0,0 +1,4 @@ +import mutable_attr + +def f(): + mutable_attr.x = 2 diff --git a/python/ql/test/query-tests/Imports/general/mutates_in_test.py b/python/ql/test/query-tests/Imports/general/mutates_in_test.py new file mode 100644 index 000000000000..60e6ef553c25 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/mutates_in_test.py @@ -0,0 +1,7 @@ +import mutable_attr +import unittest + +class T(unittest.TestCase): + + def test_foo(self): + mutable_attr.y = 3 diff --git a/python/ql/test/query-tests/Imports/general/options b/python/ql/test/query-tests/Imports/general/options new file mode 100644 index 000000000000..b91afde07678 --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=2 diff --git a/python/ql/test/query-tests/Imports/general/unittest/__init__.py b/python/ql/test/query-tests/Imports/general/unittest/__init__.py new file mode 100644 index 000000000000..083edc397d0b --- /dev/null +++ b/python/ql/test/query-tests/Imports/general/unittest/__init__.py @@ -0,0 +1,4 @@ +#Fake unittest module + +class TestCase(object): + pass diff --git a/python/ql/test/query-tests/Imports/unused/UnusedImport.expected b/python/ql/test/query-tests/Imports/unused/UnusedImport.expected new file mode 100644 index 000000000000..163bcab7b3d6 --- /dev/null +++ b/python/ql/test/query-tests/Imports/unused/UnusedImport.expected @@ -0,0 +1,5 @@ +| imports_test.py:2:1:2:23 | Import | Import of 'module2' is not used. | +| imports_test.py:6:1:6:12 | Import | Import of 'cycle' is not used. | +| imports_test.py:10:1:10:22 | Import | Import of 'top_level_cycle' is not used. | +| imports_test.py:27:1:27:25 | Import | Import of 'func2' is not used. | +| imports_test.py:34:1:34:14 | Import | Import of 'module2' is not used. | diff --git a/python/ql/test/query-tests/Imports/unused/UnusedImport.qlref b/python/ql/test/query-tests/Imports/unused/UnusedImport.qlref new file mode 100644 index 000000000000..e6bb7ab44cb0 --- /dev/null +++ b/python/ql/test/query-tests/Imports/unused/UnusedImport.qlref @@ -0,0 +1 @@ +Imports/UnusedImport.ql diff --git a/python/ql/test/query-tests/Imports/unused/imports_test.py b/python/ql/test/query-tests/Imports/unused/imports_test.py new file mode 100644 index 000000000000..5e5184b78c7e --- /dev/null +++ b/python/ql/test/query-tests/Imports/unused/imports_test.py @@ -0,0 +1,63 @@ +#Multiple imports on a single line +import module1, module2 + +#Cyclic import + +import cycle + +#Top level cyclic import + +import top_level_cycle + +#Import shadowed by loop variable + +import module + +for module in range(10): + print(module) + +#Import * used + +from module import * +from module_without_all import * + +#Unused import + +from module2 import func1 +from module2 import func2 + +module1.func +func1 + +#Duplicate import +import module1 +import module2 + +#OK -- Import used in epytext documentation. +import used_in_docs + +# L{used_in_docs} + +#OK -- Used in class. +import used_in_class + +class C(object): + + used_in_class = used_in_class + +#OK unused imports -- ODASA-3978 +from module2 import func3 as _ +from module2 import func3 as unused_but_ok +from module2 import func3 as _________ + +class Foo(object): + from bar import baz + def __init__(self): + self.foo = self.baz() + +#OK duplicate import -- Different name +import module1 as different + +#Use it +different + diff --git a/python/ql/test/query-tests/Lexical/ToDoComment/ToDoComment.expected b/python/ql/test/query-tests/Lexical/ToDoComment/ToDoComment.expected new file mode 100644 index 000000000000..c6cc8fc1ae29 --- /dev/null +++ b/python/ql/test/query-tests/Lexical/ToDoComment/ToDoComment.expected @@ -0,0 +1 @@ +| todo.py:1:1:1:65 | Comment # TO DO -- (Nothing "to do" -- this is a test for TO DO comments) | # TO DO -- (Nothing "to do" -- this is a test for TO DO comments) | diff --git a/python/ql/test/query-tests/Lexical/ToDoComment/ToDoComment.qlref b/python/ql/test/query-tests/Lexical/ToDoComment/ToDoComment.qlref new file mode 100644 index 000000000000..4568a99f3882 --- /dev/null +++ b/python/ql/test/query-tests/Lexical/ToDoComment/ToDoComment.qlref @@ -0,0 +1 @@ +Lexical/ToDoComment.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Lexical/ToDoComment/lexical_test.py b/python/ql/test/query-tests/Lexical/ToDoComment/lexical_test.py new file mode 100644 index 000000000000..a9917fd6aaa5 --- /dev/null +++ b/python/ql/test/query-tests/Lexical/ToDoComment/lexical_test.py @@ -0,0 +1,6 @@ + +#1 line that is the maximum length +print(" ") +# 2 lines that are too long +len(" ") +print(" ") diff --git a/python/ql/test/query-tests/Lexical/ToDoComment/todo.py b/python/ql/test/query-tests/Lexical/ToDoComment/todo.py new file mode 100644 index 000000000000..5e02882924b6 --- /dev/null +++ b/python/ql/test/query-tests/Lexical/ToDoComment/todo.py @@ -0,0 +1 @@ +# TO DO -- (Nothing "to do" -- this is a test for TO DO comments) diff --git a/python/ql/test/query-tests/Lexical/commented_out_code/CommentedOutCode.expected b/python/ql/test/query-tests/Lexical/commented_out_code/CommentedOutCode.expected new file mode 100644 index 000000000000..ac6b27d1a22b --- /dev/null +++ b/python/ql/test/query-tests/Lexical/commented_out_code/CommentedOutCode.expected @@ -0,0 +1,3 @@ +| test.py:15:5:16:28 | Commented out code | These comments appear to contain commented-out code. | +| test.py:21:1:72:9 | Commented out code | These comments appear to contain commented-out code. | +| test.py:78:1:85:9 | Commented out code | These comments appear to contain commented-out code. | diff --git a/python/ql/test/query-tests/Lexical/commented_out_code/CommentedOutCode.qlref b/python/ql/test/query-tests/Lexical/commented_out_code/CommentedOutCode.qlref new file mode 100644 index 000000000000..6fe55e0fa940 --- /dev/null +++ b/python/ql/test/query-tests/Lexical/commented_out_code/CommentedOutCode.qlref @@ -0,0 +1 @@ +Lexical/CommentedOutCode.ql diff --git a/python/ql/test/query-tests/Lexical/commented_out_code/FCommentedOutCode.expected b/python/ql/test/query-tests/Lexical/commented_out_code/FCommentedOutCode.expected new file mode 100644 index 000000000000..4aeac6a5d6b3 --- /dev/null +++ b/python/ql/test/query-tests/Lexical/commented_out_code/FCommentedOutCode.expected @@ -0,0 +1 @@ +| test.py:0:0:0:0 | test.py | 42 | diff --git a/python/ql/test/query-tests/Lexical/commented_out_code/FCommentedOutCode.qlref b/python/ql/test/query-tests/Lexical/commented_out_code/FCommentedOutCode.qlref new file mode 100644 index 000000000000..2776efbcfd3e --- /dev/null +++ b/python/ql/test/query-tests/Lexical/commented_out_code/FCommentedOutCode.qlref @@ -0,0 +1 @@ +Lexical/FCommentedOutCode.ql diff --git a/python/ql/test/query-tests/Lexical/commented_out_code/test.py b/python/ql/test/query-tests/Lexical/commented_out_code/test.py new file mode 100644 index 000000000000..067855b67447 --- /dev/null +++ b/python/ql/test/query-tests/Lexical/commented_out_code/test.py @@ -0,0 +1,100 @@ + +def e(): + #A real comment + some_code() + x = y + some_more_code() + "Ignore single commented out lines as it is too difficult to tell whether they are code" + #class C(object): + a_bit_more_code() + return 1 + +def f(x): + if x: + do_something() + #else: + # do_something_else() + +# Some non-code comments. +# Space immediately after scope start and between functions. +# +#class CommentedOut: +# +# def __init__(self): + +# pass +# +# def method(self): +# +# pass +# +#def g(y): +# assert y +# with y: +# # Commented out comment +# if y: +# do_something() +# else: +# do_something_else() +# +#def h(z): +# '''Doc string +# ''' +# # Commented out comment +# +# followed_by_space() + +# +# more_code() + +#def j(): +# """ Doc string """ +# pass + +#def k(): +# +# """ Doc string """ +# pass + +#def l(): +# +# """ +# Doc string +# """ +# +# pass + +# +# +# +# +#def m(): +# pass +# +# +# +some_code_to_break_up_comments() + +#with x: +# pass +#try: +# call() +#except Exception: +# pass +#except: +# pass + +def a_function_to_break_up_comments(): + pass + +# An example explaining +# something which contains +# the following code: +# +# def f(): +# call() +# x.y = z +# return x +# + + \ No newline at end of file diff --git a/python/ql/test/query-tests/Metrics/cyclo/CyclomaticComplexity.expected b/python/ql/test/query-tests/Metrics/cyclo/CyclomaticComplexity.expected new file mode 100644 index 000000000000..fd69f810c217 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/cyclo/CyclomaticComplexity.expected @@ -0,0 +1,6 @@ +| code.py:23:1:23:17 | Function nested | 4 | +| code.py:12:1:12:21 | Function two_branch | 3 | +| code.py:6:1:6:18 | Function one_branch | 2 | +| code.py:35:1:35:21 | Function exceptions | 2 | +| code.py:1:1:1:16 | Function f_linear | 1 | +| code.py:45:1:45:39 | Function must_be_positive | 1 | diff --git a/python/ql/test/query-tests/Metrics/cyclo/CyclomaticComplexity.qlref b/python/ql/test/query-tests/Metrics/cyclo/CyclomaticComplexity.qlref new file mode 100644 index 000000000000..c74ae215bb40 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/cyclo/CyclomaticComplexity.qlref @@ -0,0 +1 @@ +Metrics/CyclomaticComplexity.ql diff --git a/python/ql/test/query-tests/Metrics/cyclo/code.py b/python/ql/test/query-tests/Metrics/cyclo/code.py new file mode 100644 index 000000000000..8ca6507b3be0 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/cyclo/code.py @@ -0,0 +1,49 @@ +def f_linear(x): + y = x + z = y + return z + +def one_branch(x): + if x: + return 1 + else: + return 2 + +def two_branch(x, y): + if x: + y += 1 + else: + y += 2 + if y: + return 1 + else: + return 2 + + +def nested(x, y): + if x: + if y: + return 0 + else: + return 1 + else: + if y: + return 2 + else: + return 3 + +def exceptions(x, y): + try: + x.attr + x + y + x[y] + read() + except IOError: + pass + +#ODASA-5114 +def must_be_positive(self, obj, value): + try: + return int(value) + except: + self.error(obj, value) diff --git a/python/ql/test/query-tests/Metrics/duplicate/FLinesOfDuplicatedCode.expected b/python/ql/test/query-tests/Metrics/duplicate/FLinesOfDuplicatedCode.expected new file mode 100644 index 000000000000..51e43af9a60b --- /dev/null +++ b/python/ql/test/query-tests/Metrics/duplicate/FLinesOfDuplicatedCode.expected @@ -0,0 +1,4 @@ +| duplicate_test.py:0:0:0:0 | duplicate_test.py | 228 | +| similar.py:0:0:0:0 | similar.py | 59 | +| with_import1.py:0:0:0:0 | with_import1.py | 30 | +| with_import2.py:0:0:0:0 | with_import2.py | 30 | diff --git a/python/ql/test/query-tests/Metrics/duplicate/FLinesOfDuplicatedCode.qlref b/python/ql/test/query-tests/Metrics/duplicate/FLinesOfDuplicatedCode.qlref new file mode 100644 index 000000000000..74833a9789d7 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/duplicate/FLinesOfDuplicatedCode.qlref @@ -0,0 +1 @@ +Metrics/FLinesOfDuplicatedCode.ql diff --git a/python/ql/test/query-tests/Metrics/duplicate/FLinesOfSimilarCode.expected b/python/ql/test/query-tests/Metrics/duplicate/FLinesOfSimilarCode.expected new file mode 100644 index 000000000000..a69317fed4e9 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/duplicate/FLinesOfSimilarCode.expected @@ -0,0 +1,4 @@ +| duplicate_test.py:0:0:0:0 | duplicate_test.py | 310 | +| similar.py:0:0:0:0 | similar.py | 61 | +| with_import1.py:0:0:0:0 | with_import1.py | 30 | +| with_import2.py:0:0:0:0 | with_import2.py | 30 | diff --git a/python/ql/test/query-tests/Metrics/duplicate/FLinesOfSimilarCode.qlref b/python/ql/test/query-tests/Metrics/duplicate/FLinesOfSimilarCode.qlref new file mode 100644 index 000000000000..a2c60374e1d2 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/duplicate/FLinesOfSimilarCode.qlref @@ -0,0 +1 @@ +Metrics/FLinesOfSimilarCode.ql diff --git a/python/ql/test/query-tests/Metrics/duplicate/duplicate_test.py b/python/ql/test/query-tests/Metrics/duplicate/duplicate_test.py new file mode 100644 index 000000000000..1faf919ca960 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/duplicate/duplicate_test.py @@ -0,0 +1,321 @@ +#Code Duplication + + +#Exact duplication of function + +#Code copied from stdlib, copyright PSF. +#See http://www.python.org/download/releases/2.7/license/ + +def dis(x=None): + """Disassemble classes, methods, functions, or code. + + With no argument, disassemble the last traceback. + + """ + if x is None: + distb() + return + if isinstance(x, types.InstanceType): + x = x.__class__ + if hasattr(x, 'im_func'): + x = x.im_func + if hasattr(x, 'func_code'): + x = x.func_code + if hasattr(x, '__dict__'): + items = x.__dict__.items() + items.sort() + for name, x1 in items: + if isinstance(x1, _have_code): + print("Disassembly of %s:" % name) + try: + dis(x1) + except TypeError as msg: + print("Sorry:", msg) + print() + elif hasattr(x, 'co_code'): + disassemble(x) + elif isinstance(x, str): + disassemble_string(x) + else: + raise TypeError, \ + "don't know how to disassemble %s objects" % \ + type(x).__name__ + + +#And duplicate version + +def dis2(x=None): + """Disassemble classes, methods, functions, or code. + + With no argument, disassemble the last traceback. + + """ + if x is None: + distb() + return + if isinstance(x, types.InstanceType): + x = x.__class__ + if hasattr(x, 'im_func'): + x = x.im_func + if hasattr(x, 'func_code'): + x = x.func_code + if hasattr(x, '__dict__'): + items = x.__dict__.items() + items.sort() + for name, x1 in items: + if isinstance(x1, _have_code): + print("Disassembly of %s:" % name) + try: + dis(x1) + except TypeError as msg: + print("Sorry:", msg) + print() + elif hasattr(x, 'co_code'): + disassemble(x) + elif isinstance(x, str): + disassemble_string(x) + else: + raise TypeError, \ + "don't know how to disassemble %s objects" % \ + type(x).__name__ + +#Exactly duplicate class + +class Popen3: + """Class representing a child process. Normally, instances are created + internally by the functions popen2() and popen3().""" + + sts = -1 # Child not completed yet + + def __init__(self, cmd, capturestderr=False, bufsize=-1): + """The parameter 'cmd' is the shell command to execute in a + sub-process. On UNIX, 'cmd' may be a sequence, in which case arguments + will be passed directly to the program without shell intervention (as + with os.spawnv()). If 'cmd' is a string it will be passed to the shell + (as with os.system()). The 'capturestderr' flag, if true, specifies + that the object should capture standard error output of the child + process. The default is false. If the 'bufsize' parameter is + specified, it specifies the size of the I/O buffers to/from the child + process.""" + _cleanup() + self.cmd = cmd + p2cread, p2cwrite = os.pipe() + c2pread, c2pwrite = os.pipe() + if capturestderr: + errout, errin = os.pipe() + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + if capturestderr: + os.dup2(errin, 2) + self._run_child(cmd) + os.close(p2cread) + self.tochild = os.fdopen(p2cwrite, 'w', bufsize) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) + if capturestderr: + os.close(errin) + self.childerr = os.fdopen(errout, 'r', bufsize) + else: + self.childerr = None + + def __del__(self): + # In case the child hasn't been waited on, check if it's done. + self.poll(_deadstate=sys.maxint) + if self.sts < 0: + if _active is not None: + # Child is still running, keep us alive until we can wait on it. + _active.append(self) + + def _run_child(self, cmd): + if isinstance(cmd, basestring): + cmd = ['/bin/sh', '-c', cmd] + os.closerange(3, MAXFD) + try: + os.execvp(cmd[0], cmd) + finally: + os._exit(1) + + def poll(self, _deadstate=None): + """Return the exit status of the child process if it has finished, + or -1 if it hasn't finished yet.""" + if self.sts < 0: + try: + pid, sts = os.waitpid(self.pid, os.WNOHANG) + # pid will be 0 if self.pid hasn't terminated + if pid == self.pid: + self.sts = sts + except os.error: + if _deadstate is not None: + self.sts = _deadstate + return self.sts + + def wait(self): + """Wait for and return the exit status of the child process.""" + if self.sts < 0: + pid, sts = os.waitpid(self.pid, 0) + # This used to be a test, but it is believed to be + # always true, so I changed it to an assertion - mvl + assert pid == self.pid + self.sts = sts + return self.sts + + +class Popen3Again: + """Class representing a child process. Normally, instances are created + internally by the functions popen2() and popen3().""" + + sts = -1 # Child not completed yet + + def __init__(self, cmd, capturestderr=False, bufsize=-1): + """The parameter 'cmd' is the shell command to execute in a + sub-process. On UNIX, 'cmd' may be a sequence, in which case arguments + will be passed directly to the program without shell intervention (as + with os.spawnv()). If 'cmd' is a string it will be passed to the shell + (as with os.system()). The 'capturestderr' flag, if true, specifies + that the object should capture standard error output of the child + process. The default is false. If the 'bufsize' parameter is + specified, it specifies the size of the I/O buffers to/from the child + process.""" + _cleanup() + self.cmd = cmd + p2cread, p2cwrite = os.pipe() + c2pread, c2pwrite = os.pipe() + if capturestderr: + errout, errin = os.pipe() + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + if capturestderr: + os.dup2(errin, 2) + self._run_child(cmd) + os.close(p2cread) + self.tochild = os.fdopen(p2cwrite, 'w', bufsize) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) + if capturestderr: + os.close(errin) + self.childerr = os.fdopen(errout, 'r', bufsize) + else: + self.childerr = None + + def __del__(self): + # In case the child hasn't been waited on, check if it's done. + self.poll(_deadstate=sys.maxint) + if self.sts < 0: + if _active is not None: + # Child is still running, keep us alive until we can wait on it. + _active.append(self) + + def _run_child(self, cmd): + if isinstance(cmd, basestring): + cmd = ['/bin/sh', '-c', cmd] + os.closerange(3, MAXFD) + try: + os.execvp(cmd[0], cmd) + finally: + os._exit(1) + + def poll(self, _deadstate=None): + """Return the exit status of the child process if it has finished, + or -1 if it hasn't finished yet.""" + if self.sts < 0: + try: + pid, sts = os.waitpid(self.pid, os.WNOHANG) + # pid will be 0 if self.pid hasn't terminated + if pid == self.pid: + self.sts = sts + except os.error: + if _deadstate is not None: + self.sts = _deadstate + return self.sts + + def wait(self): + """Wait for and return the exit status of the child process.""" + if self.sts < 0: + pid, sts = os.waitpid(self.pid, 0) + # This used to be a test, but it is believed to be + # always true, so I changed it to an assertion - mvl + assert pid == self.pid + self.sts = sts + return self.sts + +#Duplicate function with identifiers changed + +def dis3(y=None): + """frobnicate classes, methods, functions, or code. + + With no argument, frobnicate the last traceback. + + """ + if y is None: + distb() + return + if isinstance(y, types.InstanceType): + y = y.__class__ + if hasattr(y, 'im_func'): + y = y.im_func + if hasattr(y, 'func_code'): + y = y.func_code + if hasattr(y, '__dict__'): + items = y.__dict__.items() + items.sort() + for name, y1 in items: + if isinstance(y1, _have_code): + print("Disassembly of %s:" % name) + try: + dis(y1) + except TypeError as msg: + print("Sorry:", msg) + print() + elif hasattr(y, 'co_code'): + frobnicate(y) + elif isinstance(y, str): + frobnicate_string(y) + else: + raise TypeError, \ + "don't know how to frobnicate %s objects" % \ + type(y).__name__ + + +#Mostly similar function with changed identifiers + +def dis5(z=None): + """splat classes, methods, functions, or code. + + With no argument, splat the last traceback. + + """ + if z is None: + distb() + return + if isinstance(z, types.InstanceType): + z = z.__class__ + if hasattr(y, 'func_code'): + y = y.func_code + if hasattr(z, '__dict__'): + items = z.__dict__.items() + items.sort() + for name, z1 in items: + if isinstance(z1, _have_code): + print("Disassembly of %s:" % name) + try: + dis(z1) + except TypeError as msg: + print("Sorry:", msg) + print() + elif hasattr(z, 'co_code'): + splat(z) + elif isinstance(z, str): + splat_string(z) + else: + raise TypeError, \ + "don't know how to splat %s objects" % \ + type(z).__name__ + + + diff --git a/python/ql/test/query-tests/Metrics/duplicate/similar.py b/python/ql/test/query-tests/Metrics/duplicate/similar.py new file mode 100644 index 000000000000..8f36c5e65ed9 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/duplicate/similar.py @@ -0,0 +1,63 @@ + + +def original(the_ast): + def walk(node, in_function, in_name_main): + def flags(): + return in_function * 2 + in_name_main + if isinstance(node, ast.Module): + for import_node in walk(node.body, in_function, in_name_main): + yield import_node + elif isinstance(node, ast.ImportFrom): + aliases = [ Alias(a.name, a.asname) for a in node.names] + yield FromImport(node.level, node.module, aliases, flags()) + elif isinstance(node, ast.Import): + aliases = [ Alias(a.name, a.asname) for a in node.names] + yield Import(aliases, flags()) + elif isinstance(node, ast.FunctionDef): + for _, child in ast.iter_fields(node): + for import_node in walk(child, True, in_name_main): + yield import_node + elif isinstance(node, list): + for n in node: + for import_node in walk(n, in_function, in_name_main): + yield import_node + return list(walk(the_ast, False, False)) + +def similar_1(the_ast): + def walk(node, in_function, in_name_main): + def flags(): + return in_function * 2 + in_name_main + if isinstance(node, ast.Module): + for import_node in walk(node.body, in_function, in_name_main): + yield import_node + elif isinstance(node, ast.ImportFrom): + aliases = [ Alias(a.name, a.asname) for a in node.names] + yield FromImport(node.level, node.module, aliases, flags()) + elif isinstance(node, ast.Import): + aliases = [ Alias(a.name, a.asname) for a in node.names] + yield Import(aliases, flags()) + elif isinstance(node, ast.FunctionDef): + for _, child in ast.iter_fields(node): + for import_node in walk(child, True, in_name_main): + yield import_node + return list(walk(the_ast, False, False)) + +def similar_2(the_ast): + def walk(node, in_function, in_name_main): + def flags(): + return in_function * 2 + in_name_main + if isinstance(node, ast.Module): + for import_node in walk(node.body, in_function, in_name_main): + yield import_node + elif isinstance(node, ast.Import): + aliases = [ Alias(a.name, a.asname) for a in node.names] + yield Import(aliases, flags()) + elif isinstance(node, ast.FunctionDef): + for _, child in ast.iter_fields(node): + for import_node in walk(child, True, in_name_main): + yield import_node + elif isinstance(node, list): + for n in node: + for import_node in walk(n, in_function, in_name_main): + yield import_node + return list(walk(the_ast, False, False)) diff --git a/python/ql/test/query-tests/Metrics/duplicate/with_import1.py b/python/ql/test/query-tests/Metrics/duplicate/with_import1.py new file mode 100644 index 000000000000..e6fa77a005f2 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/duplicate/with_import1.py @@ -0,0 +1,37 @@ +import a +import b +import c +import d +import e +import f + +# Colours +if platform.system() == 'Windows': + col_default = 0x07 + col_red = 0x0C + col_green = 0x0A +else: + col_default = '\033[0m' + col_red = '\033[91m' + col_green = '\033[92m' +col_current = None + +def set_text_colour(col): + global col_current + if col_current is None or col_current != col: + if not sys.stdout.isatty(): + pass # not on a terminal (e.g. output is being piped to file) + elif (platform.system() == 'Windows'): + # set the text colour using the Win32 API + handle = ctypes.windll.kernel32.GetStdHandle(-11) # STD_OUTPUT_HANDLE + ctypes.windll.kernel32.SetConsoleTextAttribute(handle, col) + else: + # set the text colour using a character code + sys.stdout.write(col) + col_current = col + +def report(text, col = col_default): + set_text_colour(col) + print(text) + set_text_colour(col_default) + diff --git a/python/ql/test/query-tests/Metrics/duplicate/with_import2.py b/python/ql/test/query-tests/Metrics/duplicate/with_import2.py new file mode 100644 index 000000000000..e6fa77a005f2 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/duplicate/with_import2.py @@ -0,0 +1,37 @@ +import a +import b +import c +import d +import e +import f + +# Colours +if platform.system() == 'Windows': + col_default = 0x07 + col_red = 0x0C + col_green = 0x0A +else: + col_default = '\033[0m' + col_red = '\033[91m' + col_green = '\033[92m' +col_current = None + +def set_text_colour(col): + global col_current + if col_current is None or col_current != col: + if not sys.stdout.isatty(): + pass # not on a terminal (e.g. output is being piped to file) + elif (platform.system() == 'Windows'): + # set the text colour using the Win32 API + handle = ctypes.windll.kernel32.GetStdHandle(-11) # STD_OUTPUT_HANDLE + ctypes.windll.kernel32.SetConsoleTextAttribute(handle, col) + else: + # set the text colour using a character code + sys.stdout.write(col) + col_current = col + +def report(text, col = col_default): + set_text_colour(col) + print(text) + set_text_colour(col_default) + diff --git a/python/ql/test/query-tests/Metrics/functions/FunctionStatementNestingDepth.expected b/python/ql/test/query-tests/Metrics/functions/FunctionStatementNestingDepth.expected new file mode 100644 index 000000000000..1eb36f9f4e24 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/functions/FunctionStatementNestingDepth.expected @@ -0,0 +1,9 @@ +| test.py:20:1:20:12 | Function f4 | 4 | +| test.py:50:5:50:23 | Function f4 | 4 | +| test.py:13:1:13:13 | Function f3 | 3 | +| test.py:43:5:43:23 | Function f3 | 3 | +| test.py:5:1:5:16 | Function f2 | 2 | +| test.py:35:5:35:23 | Function f2 | 2 | +| test.py:60:1:60:8 | Function g | 2 | +| test.py:2:1:2:9 | Function f1 | 1 | +| test.py:32:5:32:17 | Function f1 | 1 | diff --git a/python/ql/test/query-tests/Metrics/functions/FunctionStatementNestingDepth.qlref b/python/ql/test/query-tests/Metrics/functions/FunctionStatementNestingDepth.qlref new file mode 100644 index 000000000000..797f223792f2 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/functions/FunctionStatementNestingDepth.qlref @@ -0,0 +1 @@ +Metrics/FunctionStatementNestingDepth.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Metrics/functions/test.py b/python/ql/test/query-tests/Metrics/functions/test.py new file mode 100644 index 000000000000..ebb4c9fdfdc3 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/functions/test.py @@ -0,0 +1,63 @@ + +def f1(): + pass + +def f2(x, y, z): + if x: + pass + elif y: + yield 0 + elif z: + raise z + +def f3(a, b): + if a: + pass + else: + if b: + return 0 + +def f4(a,b): + while True: + if a: + pass + elif b: + try: + call() + except: + raise + +class C: + + def f1(self): + pass + + def f2(self, x, y): + if x: + pass + elif y: + yield 0 + elif z: + raise z + + def f3(self, a, b): + if a: + pass + else: + if b: + return 0 + + def f4(self, a, b): + while True: + if a: + pass + elif b: + try: + call() + except: + raise + +def g(): + if x: a if y else b + + diff --git a/python/ql/test/query-tests/Metrics/imports/DirectImports.expected b/python/ql/test/query-tests/Metrics/imports/DirectImports.expected new file mode 100644 index 000000000000..dd6236a1e0a2 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/imports/DirectImports.expected @@ -0,0 +1,5 @@ +| entry.py:0:0:0:0 | Module entry | 2 | +| module1.py:0:0:0:0 | Module module1 | 1 | +| module2.py:0:0:0:0 | Module module2 | 2 | +| module3.py:0:0:0:0 | Module module3 | 1 | +| module4.py:0:0:0:0 | Module module4 | 0 | diff --git a/python/ql/test/query-tests/Metrics/imports/DirectImports.qlref b/python/ql/test/query-tests/Metrics/imports/DirectImports.qlref new file mode 100644 index 000000000000..84fe2dc5805b --- /dev/null +++ b/python/ql/test/query-tests/Metrics/imports/DirectImports.qlref @@ -0,0 +1 @@ +Metrics/DirectImports.ql diff --git a/python/ql/test/query-tests/Metrics/imports/TransitiveImports.expected b/python/ql/test/query-tests/Metrics/imports/TransitiveImports.expected new file mode 100644 index 000000000000..fa9d79a6e789 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/imports/TransitiveImports.expected @@ -0,0 +1,5 @@ +| entry.py:0:0:0:0 | Module entry | 4 | +| module1.py:0:0:0:0 | Module module1 | 3 | +| module2.py:0:0:0:0 | Module module2 | 2 | +| module3.py:0:0:0:0 | Module module3 | 1 | +| module4.py:0:0:0:0 | Module module4 | 0 | diff --git a/python/ql/test/query-tests/Metrics/imports/TransitiveImports.qlref b/python/ql/test/query-tests/Metrics/imports/TransitiveImports.qlref new file mode 100644 index 000000000000..1bacdce45c28 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/imports/TransitiveImports.qlref @@ -0,0 +1 @@ +Metrics/TransitiveImports.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Metrics/imports/entry.py b/python/ql/test/query-tests/Metrics/imports/entry.py new file mode 100644 index 000000000000..b2477d61fe27 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/imports/entry.py @@ -0,0 +1,2 @@ +import module1 +import module2 \ No newline at end of file diff --git a/python/ql/test/query-tests/Metrics/imports/module1.py b/python/ql/test/query-tests/Metrics/imports/module1.py new file mode 100644 index 000000000000..1d4ff55ce87f --- /dev/null +++ b/python/ql/test/query-tests/Metrics/imports/module1.py @@ -0,0 +1 @@ +import module2 diff --git a/python/ql/test/query-tests/Metrics/imports/module2.py b/python/ql/test/query-tests/Metrics/imports/module2.py new file mode 100644 index 000000000000..6542b8234f58 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/imports/module2.py @@ -0,0 +1,2 @@ +import module3 +import module4 diff --git a/python/ql/test/query-tests/Metrics/imports/module3.py b/python/ql/test/query-tests/Metrics/imports/module3.py new file mode 100644 index 000000000000..285f88ecdb38 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/imports/module3.py @@ -0,0 +1 @@ +import module4 \ No newline at end of file diff --git a/python/ql/test/query-tests/Metrics/imports/module4.py b/python/ql/test/query-tests/Metrics/imports/module4.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Metrics/lines/CommentRatio.expected b/python/ql/test/query-tests/Metrics/lines/CommentRatio.expected new file mode 100644 index 000000000000..068a7d0c5942 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/lines/CommentRatio.expected @@ -0,0 +1 @@ +| lines.py:0:0:0:0 | Module lines | 15.0 | diff --git a/python/ql/test/query-tests/Metrics/lines/CommentRatio.qlref b/python/ql/test/query-tests/Metrics/lines/CommentRatio.qlref new file mode 100644 index 000000000000..9c3179ac5033 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/lines/CommentRatio.qlref @@ -0,0 +1 @@ +Metrics/CommentRatio.ql diff --git a/python/ql/test/query-tests/Metrics/lines/FLinesOfCode.expected b/python/ql/test/query-tests/Metrics/lines/FLinesOfCode.expected new file mode 100644 index 000000000000..d01931b5a0f2 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/lines/FLinesOfCode.expected @@ -0,0 +1 @@ +| lines.py:0:0:0:0 | Module lines | 750 | diff --git a/python/ql/test/query-tests/Metrics/lines/FLinesOfCode.qlref b/python/ql/test/query-tests/Metrics/lines/FLinesOfCode.qlref new file mode 100644 index 000000000000..2ac69b8f67a8 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/lines/FLinesOfCode.qlref @@ -0,0 +1 @@ +Metrics/FLinesOfCode.ql diff --git a/python/ql/test/query-tests/Metrics/lines/lines.py b/python/ql/test/query-tests/Metrics/lines/lines.py new file mode 100644 index 000000000000..81d922e84d33 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/lines/lines.py @@ -0,0 +1,1200 @@ +line = 1 + +line = 2 +line = 3 + +line = 4 +line = 5 +line = 6 +line = 7 + +line = 8 +line = 9 + + +line = 10 +line = 11 +line = 12 + +line = 13 +#comment 1 +line = 14 +line = 15 +line = 16 +line = 17 + +line = 18 +line = 19 +line = 20 +line = 21 +line = 22 +#comment 2 +#comment 3 +#comment 4 + +line = 23 +line = 24 +line = 25 +line = 26 + +line = 27 +line = 28 +line = 29 +line = 30 +line = 31 +line = 32 +#comment 5 +line = 33 +line = 34 +line = 35 + +line = 36 + + +line = 37 +line = 38 +line = 39 +#comment 6 +line = 40 +line = 41 + + +line = 42 +line = 43 + +line = 44 +#comment 7 +line = 45 +#comment 8 + +line = 46 +line = 47 +line = 48 + +line = 49 +line = 50 +line = 51 +line = 52 +line = 53 +#comment 9 +line = 54 +line = 55 +line = 56 +line = 57 +line = 58 +#comment 10 +line = 59 +#comment 11 + +#comment 12 +line = 60 +line = 61 +#comment 13 +line = 62 +line = 63 +#comment 14 +line = 64 +line = 65 +line = 66 +#comment 15 +#comment 16 +line = 67 +#comment 17 + +line = 68 +line = 69 +line = 70 +line = 71 +line = 72 +line = 73 +#comment 18 +line = 74 + +line = 75 +#comment 19 +#comment 20 +line = 76 +line = 77 +#comment 21 +line = 78 +line = 79 +line = 80 + +line = 81 +line = 82 +line = 83 +#comment 22 +line = 84 +line = 85 +#comment 23 +line = 86 +#comment 24 +line = 87 +#comment 25 +line = 88 +line = 89 +#comment 26 +line = 90 + +line = 91 +line = 92 +line = 93 +line = 94 +line = 95 +#comment 27 +#comment 28 +#comment 29 +line = 96 +line = 97 +#comment 30 +line = 98 +line = 99 +line = 100 +line = 101 +line = 102 + +line = 103 +line = 104 + +line = 105 +line = 106 +line = 107 + + +line = 108 +line = 109 + +line = 110 +#comment 31 +line = 111 +line = 112 +line = 113 + +line = 114 +line = 115 + +line = 116 +#comment 32 +line = 117 + +line = 118 +line = 119 + +line = 120 +line = 121 +line = 122 +line = 123 +line = 124 +line = 125 + +#comment 33 + +#comment 34 + +line = 126 +line = 127 + +line = 128 +line = 129 + +line = 130 +line = 131 + +line = 132 + +line = 133 +line = 134 +line = 135 +#comment 35 +line = 136 +#comment 36 + + +#comment 37 +#comment 38 +line = 137 +line = 138 +line = 139 +line = 140 +line = 141 +line = 142 +line = 143 +line = 144 +line = 145 +line = 146 +line = 147 +line = 148 +line = 149 +line = 150 + + +line = 151 + +line = 152 +line = 153 +line = 154 +line = 155 + +#comment 39 +#comment 40 + +line = 156 +line = 157 +line = 158 +#comment 41 +line = 159 + +line = 160 +line = 161 +line = 162 + +#comment 42 +line = 163 +line = 164 +#comment 43 +line = 165 +line = 166 +line = 167 + +line = 168 +line = 169 +line = 170 +#comment 44 +line = 171 +line = 172 + + +line = 173 +line = 174 + +line = 175 + +#comment 45 +line = 176 + +#comment 46 +line = 177 +line = 178 + +line = 179 +line = 180 +#comment 47 + +line = 181 +line = 182 + +line = 183 +#comment 48 +line = 184 +line = 185 +line = 186 +line = 187 +line = 188 +line = 189 +line = 190 + +line = 191 + +line = 192 +line = 193 +#comment 49 +line = 194 +line = 195 +#comment 50 +line = 196 +line = 197 + +line = 198 +line = 199 +line = 200 + + + + +line = 201 +#comment 51 +line = 202 +line = 203 +line = 204 + +line = 205 + +#comment 52 +line = 206 +line = 207 +line = 208 +line = 209 +#comment 53 +line = 210 +line = 211 + +line = 212 +line = 213 +#comment 54 +line = 214 +line = 215 +line = 216 +line = 217 +#comment 55 +line = 218 + +line = 219 +line = 220 + +line = 221 + +line = 222 +line = 223 +line = 224 +line = 225 +line = 226 + + +line = 227 +line = 228 +line = 229 + +line = 230 +line = 231 + + +line = 232 + +line = 233 + +line = 234 + + +line = 235 +line = 236 +line = 237 +line = 238 +line = 239 + +line = 240 +line = 241 +#comment 56 +line = 242 +line = 243 +line = 244 +#comment 57 +line = 245 + +line = 246 + +line = 247 + +line = 248 +line = 249 + +line = 250 +line = 251 +line = 252 +line = 253 +line = 254 + +line = 255 + +line = 256 +#comment 58 +line = 257 +line = 258 +line = 259 +line = 260 +line = 261 +line = 262 +line = 263 +line = 264 +line = 265 +line = 266 +line = 267 +line = 268 +line = 269 +line = 270 + + + +#comment 59 + +line = 271 +#comment 60 +line = 272 + +line = 273 +line = 274 + + +line = 275 +line = 276 +line = 277 +#comment 61 +#comment 62 +line = 278 +line = 279 +#comment 63 +line = 280 +line = 281 +line = 282 + +line = 283 +line = 284 +line = 285 + +#comment 64 +line = 286 + + +line = 287 +line = 288 + +line = 289 +#comment 65 +line = 290 +line = 291 +line = 292 +line = 293 +line = 294 +line = 295 +#comment 66 + +#comment 67 + +line = 296 +line = 297 + +line = 298 +line = 299 +#comment 68 +line = 300 +line = 301 +line = 302 +#comment 69 + +line = 303 +line = 304 +line = 305 +line = 306 +line = 307 +line = 308 +line = 309 +line = 310 +line = 311 +line = 312 +line = 313 +line = 314 + +#comment 70 +#comment 71 +line = 315 + +line = 316 + +line = 317 +line = 318 + +line = 319 +line = 320 +line = 321 +line = 322 + +line = 323 + +line = 324 + +line = 325 +line = 326 +line = 327 +line = 328 +line = 329 +#comment 72 + + + +line = 330 +line = 331 + +line = 332 +line = 333 + +line = 334 + +line = 335 +#comment 73 +line = 336 +line = 337 +line = 338 +#comment 74 +line = 339 +line = 340 +#comment 75 + +line = 341 +line = 342 +line = 343 +line = 344 +#comment 76 +#comment 77 + +#comment 78 +#comment 79 +line = 345 +line = 346 +line = 347 +line = 348 +#comment 80 +line = 349 +line = 350 +line = 351 +#comment 81 +line = 352 +line = 353 +#comment 82 +#comment 83 + +line = 354 + +line = 355 +line = 356 + +line = 357 + + +line = 358 +line = 359 +line = 360 + +line = 361 +line = 362 + +line = 363 +#comment 84 +line = 364 +line = 365 +line = 366 +line = 367 + + +line = 368 +line = 369 +line = 370 + +#comment 85 +line = 371 +line = 372 +line = 373 +line = 374 +line = 375 + +line = 376 +line = 377 +line = 378 +#comment 86 +#comment 87 +#comment 88 +line = 379 + +line = 380 +line = 381 + + +line = 382 +line = 383 +line = 384 +line = 385 + +line = 386 +#comment 89 +line = 387 +line = 388 +#comment 90 +line = 389 +line = 390 +line = 391 + + +line = 392 +line = 393 +line = 394 +line = 395 + +line = 396 +line = 397 + + +#comment 91 +line = 398 +line = 399 +line = 400 +line = 401 + +#comment 92 +line = 402 +line = 403 +line = 404 +line = 405 + +line = 406 + + +#comment 93 +line = 407 +line = 408 +#comment 94 +line = 409 +line = 410 + + +#comment 95 +#comment 96 +#comment 97 +line = 411 +line = 412 +line = 413 + +line = 414 +line = 415 +line = 416 +line = 417 + +line = 418 +line = 419 +#comment 98 +line = 420 +line = 421 + +#comment 99 +line = 422 + + +line = 423 + +line = 424 +line = 425 +line = 426 +#comment 100 +line = 427 + + +line = 428 +line = 429 +line = 430 +line = 431 + +line = 432 +line = 433 + +line = 434 +line = 435 +line = 436 + +line = 437 +#comment 101 +#comment 102 +line = 438 +line = 439 +line = 440 +line = 441 + +line = 442 + +line = 443 +line = 444 +line = 445 + +#comment 103 +line = 446 +line = 447 + +line = 448 + +line = 449 + +line = 450 +line = 451 +line = 452 +line = 453 +line = 454 +line = 455 +line = 456 +line = 457 + + +#comment 104 +line = 458 +#comment 105 +line = 459 +line = 460 +#comment 106 +#comment 107 +line = 461 +#comment 108 +#comment 109 +line = 462 +line = 463 +line = 464 +#comment 110 +line = 465 +line = 466 +line = 467 +line = 468 +line = 469 +#comment 111 +line = 470 +line = 471 + +line = 472 + +line = 473 + + +line = 474 +line = 475 +line = 476 +line = 477 + +line = 478 +line = 479 + + +line = 480 + +line = 481 +line = 482 + +line = 483 +line = 484 +line = 485 +line = 486 +line = 487 +line = 488 +#comment 112 +#comment 113 +line = 489 +line = 490 +line = 491 +#comment 114 +line = 492 +line = 493 +line = 494 +#comment 115 +line = 495 +line = 496 +line = 497 + +line = 498 + +line = 499 + +line = 500 +line = 501 +#comment 116 + + +#comment 117 +#comment 118 +#comment 119 +line = 502 +line = 503 +line = 504 +line = 505 +line = 506 + +line = 507 +line = 508 +line = 509 + +line = 510 +#comment 120 +line = 511 + +#comment 121 +#comment 122 +#comment 123 + +#comment 124 +line = 512 +line = 513 +line = 514 +line = 515 +line = 516 +line = 517 + + + +line = 518 +line = 519 +line = 520 +line = 521 +line = 522 +line = 523 + +#comment 125 +line = 524 +line = 525 +line = 526 +line = 527 + +#comment 126 +line = 528 +line = 529 +#comment 127 + + +line = 530 +line = 531 +#comment 128 + +line = 532 + +#comment 129 +line = 533 + +line = 534 +line = 535 +line = 536 +line = 537 +line = 538 + + +line = 539 +line = 540 +line = 541 +line = 542 +line = 543 +line = 544 +line = 545 + +line = 546 +line = 547 +#comment 130 +line = 548 +line = 549 + +line = 550 + +line = 551 +line = 552 +line = 553 +line = 554 +line = 555 +line = 556 +line = 557 +line = 558 +line = 559 +line = 560 +line = 561 +line = 562 +line = 563 +line = 564 +line = 565 + +#comment 131 +line = 566 +line = 567 +line = 568 +line = 569 + +line = 570 +line = 571 + +line = 572 + +line = 573 +line = 574 + +line = 575 +#comment 132 +line = 576 +line = 577 +line = 578 +line = 579 + +line = 580 +line = 581 +line = 582 + +line = 583 +line = 584 + +line = 585 +line = 586 + + + +line = 587 + +line = 588 +line = 589 + +line = 590 +line = 591 +line = 592 +#comment 133 +#comment 134 +line = 593 +#comment 135 + +line = 594 + +line = 595 +#comment 136 +line = 596 +line = 597 +line = 598 + + +line = 599 +line = 600 +#comment 137 +line = 601 + +line = 602 +line = 603 + +#comment 138 + +line = 604 +line = 605 +line = 606 +line = 607 +line = 608 +line = 609 +line = 610 + + + +line = 611 +line = 612 +line = 613 +line = 614 +line = 615 +#comment 139 +#comment 140 +line = 616 +line = 617 + + +#comment 141 +line = 618 +line = 619 +line = 620 +line = 621 +line = 622 +line = 623 +line = 624 +line = 625 +line = 626 + +line = 627 + +line = 628 +line = 629 +line = 630 +line = 631 +line = 632 + +line = 633 + +line = 634 +line = 635 +line = 636 +line = 637 +line = 638 +line = 639 +line = 640 + +#comment 142 + +line = 641 + +#comment 143 + +line = 642 +line = 643 +line = 644 + + +line = 645 + +line = 646 +#comment 144 + +line = 647 +line = 648 +line = 649 +line = 650 +line = 651 +line = 652 +#comment 145 +line = 653 +line = 654 +line = 655 +line = 656 +line = 657 +line = 658 +line = 659 + + +line = 660 + +#comment 146 + +line = 661 + +#comment 147 +line = 662 +#comment 148 +line = 663 + +line = 664 + + +line = 665 +line = 666 +line = 667 + + +line = 668 +line = 669 +line = 670 + +line = 671 +line = 672 +#comment 149 +#comment 150 +line = 673 +line = 674 +line = 675 +line = 676 +#comment 151 +line = 677 +line = 678 + +line = 679 +line = 680 +line = 681 + +line = 682 +line = 683 +line = 684 + +#comment 152 +line = 685 +line = 686 +line = 687 +line = 688 +line = 689 +line = 690 +line = 691 + +line = 692 + +#comment 153 + +line = 693 + +#comment 154 + +line = 694 +line = 695 +line = 696 +#comment 155 +line = 697 +line = 698 +#comment 156 +line = 699 +line = 700 +#comment 157 + + +#comment 158 +line = 701 + + +#comment 159 +line = 702 +line = 703 + +line = 704 +#comment 160 +line = 705 +#comment 161 + +line = 706 +line = 707 +line = 708 +line = 709 +line = 710 +#comment 162 +line = 711 + +line = 712 +line = 713 +line = 714 +#comment 163 +line = 715 + +line = 716 + +#comment 164 +#comment 165 +line = 717 +#comment 166 +line = 718 +line = 719 + +line = 720 +line = 721 +line = 722 + + +line = 723 +#comment 167 +#comment 168 +line = 724 +line = 725 +line = 726 +line = 727 +line = 728 + +#comment 169 +#comment 170 + +line = 729 +line = 730 + +line = 731 +line = 732 +#comment 171 +line = 733 +#comment 172 + +line = 734 + +line = 735 +line = 736 +line = 737 +line = 738 +line = 739 +line = 740 +line = 741 +line = 742 +line = 743 + +line = 744 +line = 745 +line = 746 +#comment 173 +line = 747 +#comment 174 +#comment 175 + +#comment 176 +line = 748 +line = 749 +line = 750 +#comment 177 +#comment 178 +#comment 179 + +#comment 180 -- Exactly 1200 lines diff --git a/python/ql/test/query-tests/Metrics/ratios/CodeRatio.expected b/python/ql/test/query-tests/Metrics/ratios/CodeRatio.expected new file mode 100644 index 000000000000..0c5d498b00de --- /dev/null +++ b/python/ql/test/query-tests/Metrics/ratios/CodeRatio.expected @@ -0,0 +1 @@ +| doc_string.py:0:0:0:0 | Module doc_string | 24.0 | diff --git a/python/ql/test/query-tests/Metrics/ratios/CodeRatio.ql b/python/ql/test/query-tests/Metrics/ratios/CodeRatio.ql new file mode 100644 index 000000000000..545453e93ebd --- /dev/null +++ b/python/ql/test/query-tests/Metrics/ratios/CodeRatio.ql @@ -0,0 +1,7 @@ + +import python + +from Module m, ModuleMetrics mm +where mm = m.getMetrics() and mm.getNumberOfLines() > 0 +select m, 100.0 * ((float)mm.getNumberOfLinesOfCode() / (float)mm.getNumberOfLines()) as ratio +order by ratio desc \ No newline at end of file diff --git a/python/ql/test/query-tests/Metrics/ratios/CommentRatio.expected b/python/ql/test/query-tests/Metrics/ratios/CommentRatio.expected new file mode 100644 index 000000000000..43c2bcf7c111 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/ratios/CommentRatio.expected @@ -0,0 +1 @@ +| doc_string.py:0:0:0:0 | Module doc_string | 12.0 | diff --git a/python/ql/test/query-tests/Metrics/ratios/CommentRatio.qlref b/python/ql/test/query-tests/Metrics/ratios/CommentRatio.qlref new file mode 100644 index 000000000000..dc273e169828 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/ratios/CommentRatio.qlref @@ -0,0 +1 @@ +Metrics/CommentRatio.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Metrics/ratios/DocStringRatio.expected b/python/ql/test/query-tests/Metrics/ratios/DocStringRatio.expected new file mode 100644 index 000000000000..76e6e095d636 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/ratios/DocStringRatio.expected @@ -0,0 +1 @@ +| doc_string.py:0:0:0:0 | Module doc_string | 50.0 | diff --git a/python/ql/test/query-tests/Metrics/ratios/DocStringRatio.qlref b/python/ql/test/query-tests/Metrics/ratios/DocStringRatio.qlref new file mode 100644 index 000000000000..ec66c5cdc834 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/ratios/DocStringRatio.qlref @@ -0,0 +1 @@ +Metrics/DocStringRatio.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Metrics/ratios/doc_string.py b/python/ql/test/query-tests/Metrics/ratios/doc_string.py new file mode 100644 index 000000000000..13fe12c5f2dc --- /dev/null +++ b/python/ql/test/query-tests/Metrics/ratios/doc_string.py @@ -0,0 +1,50 @@ +''' +A long docstring... + +The Zen of Python, by Tim Peters + +Beautiful is better than ugly. +Explicit is better than implicit. +Simple is better than complex. +Complex is better than complicated. +Flat is better than nested. +Sparse is better than dense. +Readability counts. +Special cases aren't special enough to break the rules. +Although practicality beats purity. +Errors should never pass silently. +Unless explicitly silenced. +In the face of ambiguity, refuse the temptation to guess. +There should be one-- and preferably only one --obvious way to do it. +Although that way may not be obvious at first unless you're Dutch. +Now is better than never. +Although never is often better than *right* now. +If the implementation is hard to explain, it's a bad idea. +If the implementation is easy to explain, it may be a good idea. +Namespaces are one honking great idea -- let's do more of those! +''' + +#25 lines of docstring in a 50 line module: 50% +#12 lines of code in a 50 line module: 24% +#6 lines of comment in a 50 line module: 12% + +#And some code +import nonexistent + +def function(): + meaning_of_life = 42 + return meaning_of_life + +class C: + + def __init__(self): + stuff() + more_stuff() + yet_more_stuff() + #Pointless return + return + + def m(self): + pass + +# A comment on the last line diff --git a/python/ql/test/query-tests/Metrics/tests/FNumberOfTests.expected b/python/ql/test/query-tests/Metrics/tests/FNumberOfTests.expected new file mode 100644 index 000000000000..a786f6423917 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/tests/FNumberOfTests.expected @@ -0,0 +1 @@ +| test.py:0:0:0:0 | test.py | 4 | diff --git a/python/ql/test/query-tests/Metrics/tests/FNumberOfTests.qlref b/python/ql/test/query-tests/Metrics/tests/FNumberOfTests.qlref new file mode 100644 index 000000000000..e7301c3b2302 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/tests/FNumberOfTests.qlref @@ -0,0 +1 @@ +Metrics/FNumberOfTests.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Metrics/tests/pytest.py b/python/ql/test/query-tests/Metrics/tests/pytest.py new file mode 100644 index 000000000000..680efc88e4bf --- /dev/null +++ b/python/ql/test/query-tests/Metrics/tests/pytest.py @@ -0,0 +1,3 @@ +""" +Fake pytest module +""" \ No newline at end of file diff --git a/python/ql/test/query-tests/Metrics/tests/test.py b/python/ql/test/query-tests/Metrics/tests/test.py new file mode 100644 index 000000000000..24c00456afe1 --- /dev/null +++ b/python/ql/test/query-tests/Metrics/tests/test.py @@ -0,0 +1,36 @@ + +class Test: + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_1(self): + pass + + def test_2(self): + pass + + def test_3(self): + pass + + +import pytest + +def test_something(x): + pass + + +def with_doctest(): + ''' + >>> 1 + 1 + 2 + + >>> 1 * 1 + 1 + ''' + +def without_doctest(): + pass \ No newline at end of file diff --git a/python/ql/test/query-tests/Resources/Dataflow.expected b/python/ql/test/query-tests/Resources/Dataflow.expected new file mode 100644 index 000000000000..ed7be1b36002 --- /dev/null +++ b/python/ql/test/query-tests/Resources/Dataflow.expected @@ -0,0 +1,119 @@ +| f1_0 = open() | open | | +| f1_1 = MethodCallsiteRefinement(f1_0) | open | | +| f1_2 = MethodCallsiteRefinement(f1_1) | closed | exit | +| f2_0 = open() | open | exit | +| f3_0 = open() | open | | +| f3_1 = MethodCallsiteRefinement(f3_0) | closed | exit | +| f4_0 = with | closed | | +| f4_1 = MethodCallsiteRefinement(f4_0) | closed | exit | +| f5_0 = open() | open | | +| f5_1 = MethodCallsiteRefinement(f5_0) | open | | +| f5_2 = MethodCallsiteRefinement(f5_1) | closed | exit | +| f5_3 = phi(f5_0, f5_1) | open | | +| f6_0 = None | closed | | +| f6_1 = open() | open | | +| f6_2 = MethodCallsiteRefinement(f6_1) | open | | +| f6_3 = phi(f6_0, f6_1, f6_2) | open | | +| f6_4 = SingleSuccessorGuard(f6_3) [true] | open | | +| f6_5 = Pi(f6_2) [true] | open | | +| f6_6 = MethodCallsiteRefinement(f6_5) | closed | | +| f6_7 = Pi(f6_2) [false] | closed | | +| f6_8 = phi(f6_6, f6_7) | closed | exit | +| f7_0 = None | closed | | +| f7_1 = open() | open | | +| f7_2 = MethodCallsiteRefinement(f7_1) | open | | +| f7_3 = phi(f7_0, f7_1, f7_2) | open | | +| f7_4 = SingleSuccessorGuard(f7_3) [true] | open | | +| f7_5 = Pi(f7_2) [true] | open | | +| f7_6 = MethodCallsiteRefinement(f7_5) | closed | | +| f7_7 = Pi(f7_2) [false] | closed | | +| f7_8 = phi(f7_6, f7_7) | closed | exit | +| f8_0 = None | closed | | +| f8_1 = open() | open | | +| f8_2 = MethodCallsiteRefinement(f8_1) | open | | +| f8_3 = phi(f8_0, f8_1, f8_2) | open | | +| f8_4 = SingleSuccessorGuard(f8_3) [true] | open | | +| f8_5 = Pi(f8_2) [true] | closed | | +| f8_6 = MethodCallsiteRefinement(f8_5) | closed | | +| f8_7 = Pi(f8_2) [false] | open | | +| f8_8 = phi(f8_6, f8_7) | open | exit | +| f9_0 = None | closed | | +| f9_1 = open() | open | | +| f9_2 = MethodCallsiteRefinement(f9_1) | open | | +| f9_3 = phi(f9_0, f9_1, f9_2) | open | | +| f9_4 = SingleSuccessorGuard(f9_3) [true] | open | | +| f9_5 = Pi(f9_2) [true] | closed | | +| f9_6 = MethodCallsiteRefinement(f9_5) | closed | | +| f9_7 = Pi(f9_2) [false] | open | | +| f9_8 = phi(f9_6, f9_7) | open | exit | +| f10_0 = open() | open | | +| f10_1 = MethodCallsiteRefinement(f10_0) | open | | +| f10_2 = MethodCallsiteRefinement(f10_1) | open | | +| f10_3 = MethodCallsiteRefinement(f10_2) | closed | | +| f10_4 = phi(f10_0, f10_1, f10_2, f10_3) | open | | +| f10_5 = MethodCallsiteRefinement(f10_4) | closed | | +| f10_6 = phi(f10_3, f10_5) | closed | exit | +| f11_0 = open() | open | | +| f11_1 = MethodCallsiteRefinement(f11_0) | open | | +| f11_2 = MethodCallsiteRefinement(f11_1) | open | | +| f11_3 = MethodCallsiteRefinement(f11_2) | closed | | +| f11_4 = phi(f11_0, f11_1, f11_2, f11_3) | open | | +| f11_5 = MethodCallsiteRefinement(f11_4) | closed | | +| f11_6 = phi(f11_3, f11_5) | closed | exit | +| f12_0 = open() | open | | +| f12_1 = MethodCallsiteRefinement(f12_0) | open | | +| f12_2 = MethodCallsiteRefinement(f12_1) | open | | +| f12_3 = MethodCallsiteRefinement(f12_2) | closed | | +| f12_4 = phi(f12_0, f12_1, f12_2, f12_3) | open | | +| f12_5 = MethodCallsiteRefinement(f12_4) | closed | | +| f12_6 = phi(f12_3, f12_5) | closed | exit | +| f13_0 = open() | open | | +| f13_1 = MethodCallsiteRefinement(f13_0) | open | exit | +| f14_0 = opener_func2() | open | | +| f14_1 = MethodCallsiteRefinement(f14_0) | open | | +| f14_2 = MethodCallsiteRefinement(f14_1) | closed | exit | +| f15_0 = opener_func2() | open | | +| f15_1 = ArgumentRefinement(f15_0) | closed | exit | +| f16_0 = ScopeEntryDefinition | closed | | +| f16_1 = open() | open | | +| f16_2 = MethodCallsiteRefinement(f16_1) | open | | +| f16_3 = MethodCallsiteRefinement(f16_2) | closed | | +| f16_4 = phi(f16_0, f16_1, f16_2, f16_3) | open | | +| f16_5 = phi(f16_3, f16_4) | open | exit | +| f17_0 = open() | open | | +| f17_1 = MethodCallsiteRefinement(f17_0) | open | | +| f17_2 = MethodCallsiteRefinement(f17_1) | open | | +| f17_3 = MethodCallsiteRefinement(f17_2) | closed | | +| f17_4 = phi(f17_0, f17_1, f17_2, f17_3) | open | | +| f17_5 = MethodCallsiteRefinement(f17_4) | closed | | +| f17_6 = phi(f17_3, f17_5) | closed | exit | +| f18_0 = open() | closed | | +| f18_1 = MethodCallsiteRefinement(f18_0) | closed | exit | +| f20_0 = open() | open | | +| f20_1 = ArgumentRefinement(f20_0) | closed | exit | +| f21_0 = open() | open | | +| f21_1 = MethodCallsiteRefinement(f21_0) | open | | +| f21_2 = MethodCallsiteRefinement(f21_1) | closed | | +| f21_3 = phi(f21_1, f21_2) | open | | +| f21_4 = phi(f21_0, f21_1, f21_2) | open | | +| f21_5 = SingleSuccessorGuard(f21_4) [true] | open | | +| f21_6 = Pi(f21_3) [true] | open | | +| f21_7 = MethodCallsiteRefinement(f21_6) | closed | | +| f21_8 = Pi(f21_3) [false] | closed | | +| f21_9 = phi(f21_7, f21_8) | closed | exit | +| f22_0 = open() | open | | +| f22_1 = MethodCallsiteRefinement(f22_0) | open | | +| f22_2 = MethodCallsiteRefinement(f22_1) | closed | | +| f22_3 = phi(f22_1, f22_2) | open | | +| f22_4 = phi(f22_0, f22_1, f22_2) | open | | +| f22_5 = SingleSuccessorGuard(f22_4) [true] | open | | +| f22_6 = Pi(f22_3) [true] | closed | | +| f22_7 = MethodCallsiteRefinement(f22_6) | closed | | +| f22_8 = Pi(f22_3) [false] | open | | +| f22_9 = phi(f22_7, f22_8) | open | exit | +| f_0 = FunctionExpr | closed | exit | +| file_0 = open() | open | | +| file_1 = open() | open | | +| file_2 = None | closed | | +| file_3 = phi(file_0, file_1, file_2) | open | exit | +| fp_0 = ParameterDefinition | closed | exit | diff --git a/python/ql/test/query-tests/Resources/Dataflow.ql b/python/ql/test/query-tests/Resources/Dataflow.ql new file mode 100644 index 000000000000..ee92ee981c32 --- /dev/null +++ b/python/ql/test/query-tests/Resources/Dataflow.ql @@ -0,0 +1,16 @@ + +import python +import Resources.FileOpen + + +from EssaVariable v, EssaDefinition def, string open, string exit +where def = v.getDefinition() and v.getSourceVariable().getName().charAt(0) = "f" and +( + var_is_open(v, _) and open = "open" + or + not var_is_open(v, _) and open = "closed" +) +and +if BaseFlow::reaches_exit(v) then exit = "exit" else exit = "" + +select v.getRepresentation() + " = " + v.getDefinition().getRepresentation(), open, exit diff --git a/python/ql/test/query-tests/Resources/FileNotAlwaysClosed.expected b/python/ql/test/query-tests/Resources/FileNotAlwaysClosed.expected new file mode 100644 index 000000000000..c0a6c413333f --- /dev/null +++ b/python/ql/test/query-tests/Resources/FileNotAlwaysClosed.expected @@ -0,0 +1,9 @@ +| resources_test.py:4:10:4:25 | open() | File may not be closed if an exception is raised. | +| resources_test.py:9:10:9:25 | open() | File is opened but is not closed. | +| resources_test.py:49:14:49:29 | open() | File is opened but is not closed. | +| resources_test.py:58:14:58:29 | open() | File is opened but is not closed. | +| resources_test.py:79:11:79:26 | open() | File may not be closed if an exception is raised. | +| resources_test.py:108:11:108:20 | open() | File is opened but is not closed. | +| resources_test.py:112:11:112:28 | opener_func2() | File may not be closed if an exception is raised. | +| resources_test.py:129:15:129:24 | open() | File is opened but is not closed. | +| resources_test.py:237:11:237:26 | open() | File is opened but is not closed. | diff --git a/python/ql/test/query-tests/Resources/FileNotAlwaysClosed.qlref b/python/ql/test/query-tests/Resources/FileNotAlwaysClosed.qlref new file mode 100644 index 000000000000..37e9a0766805 --- /dev/null +++ b/python/ql/test/query-tests/Resources/FileNotAlwaysClosed.qlref @@ -0,0 +1 @@ +Resources/FileNotAlwaysClosed.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Resources/resources_test.py b/python/ql/test/query-tests/Resources/resources_test.py new file mode 100644 index 000000000000..73fa88b44016 --- /dev/null +++ b/python/ql/test/query-tests/Resources/resources_test.py @@ -0,0 +1,246 @@ +#File not always closed + +def not_close1(): + f1 = open("filename") + f1.write("Error could occur") + f1.close() + +def not_close2(): + f2 = open("filename") + +def closed3(): + f3 = open("filename") + f3.close() + +def closed4(): + with open("filename") as f4: + f4.write("Error could occur") + +def closed5(): + f5 = open("filename") + try: + f5.write("Error could occur") + finally: + f5.close() + +#Correctly guarded close() +def closed6(): + f6 = None + try: + f6 = open("filename") + f6.write("Error could occur") + finally: + if f6: + f6.close() + +def closed7(): + f7 = None + try: + f7 = open("filename") + f7.write("Error could occur") + finally: + if not f7 is None: + f7.close() + +#Incorrectly guarded close() +def not_closed8(): + f8 = None + try: + f8 = open("filename") + f8.write("Error could occur") + finally: + if f8 is None: + f8.close() + +def not_closed9(): + f9 = None + try: + f9 = open("filename") + f9.write("Error could occur") + finally: + if not f9: + f9.close() + +def not_closed_but_cant_tell_locally(): + return open("filename") + +#Closed by handling the correct exception +def closed10(): + f10 = open("filename") + try: + f10.write("IOError could occur") + f10.write("IOError could occur") + f10.close() + except IOError: + f10.close() + +#Not closed by handling the wrong exception +def not_closed11(): + f11 = open("filename") + try: + f11.write("IOError could occur") + f11.write("IOError could occur") + f11.close() + except AttributeError: + f11.close() + +def doesnt_raise(): + pass + +def mostly_closed12(): + f12 = open("filename") + try: + f12.write("IOError could occur") + f12.write("IOError could occur") + doesnt_raise("Potential false positive here") + f12.close() + except IOError: + f12.close() + +def opener_func1(name): + return open(name) + +def opener_func2(name): + t1 = opener_func1(name) + return t1 + +def not_closed13(name): + f13 = open(name) + f13.write("Hello") + +def may_not_be_closed14(name): + f14 = opener_func2(name) + f14.write("Hello") + f14.close() + +def closer1(t2): + t2.close() + +def closer2(t3): + closer1(t3) + +def closed15(): + f15 = opener_func2() + closer2(f15) + + +def may_not_be_closed16(name): + try: + f16 = open(name) + f16.write("Hello") + f16.close() + except IOError: + pass + +def may_raise(): + if random(): + raise ValueError() + +#Not handling all exceptions, but we'll tolerate the false negative +def not_closed17(): + f17 = open("filename") + try: + f17.write("IOError could occur") + f17.write("IOError could occur") + may_raise("ValueError could occur") # FN here. + f17.close() + except IOError: + f17.close() + +#ODASA-3779 +#With statement will close the fp +def closed18(path): + try: + f18 = open(path) + except IOError as ex: + print(ex) + raise ex + with f18: + f18.read() + +class Closed19(object): + + def __enter__(self): + self.fd = open("Filename") + + def __exit__(self, *args): + self.fd.close() + +class FileWrapper(object): + + def __init__(self, fp): + self.fp = fp + +def closed20(path): + f20 = open(path) + return FileWrapper(f20) + +#ODASA-3105 +def run(nodes_module): + use_file = len(sys.argv) > 1 + if use_file: + out = open(sys.argv[1], 'w', encoding='utf-8') + else: + out = sys.stdout + try: + out.write("spam") + finally: + if use_file: + out.close() + +#ODASA-3515 +class GraphVizTrapWriter(object): + + def __init__(self, out): + if out is None: + self.out = sys.stdout + else: + self.out = open(out, 'w') + self.pool = GraphVizIdPool(self.out) + + def __del__(self): + if self.out != sys.stdout: + self.out.close() + +#Returned as part of tuple +def f(name, path): + try: + path = path.attr + file = open(path, 'rb') + except AttributeError: + # ExtensionLoader has not attribute get_filename, instead it has a + # path attribute that we can use to retrieve the module path + try: + path = path.other_attr + file = open(path, 'rb') + except AttributeError: + path = name + file = None + + return file, path + + +#ODASA-5891 +def closed21(path): + f21 = open(path, "wb") + try: + f21.write(b"foo") + may_raise() + if foo: + f21.close() + finally: + if not f21.closed: + f21.close() + + +def not_closed22(path): + f22 = open(path, "wb") + try: + f22.write(b"foo") + may_raise() + if foo: + f22.close() + finally: + if f22.closed: # Wrong sense + f22.close() + diff --git a/python/ql/test/query-tests/Security/CWE-022/PathInjection.expected b/python/ql/test/query-tests/Security/CWE-022/PathInjection.expected new file mode 100644 index 000000000000..41e99f6c16b6 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-022/PathInjection.expected @@ -0,0 +1,37 @@ +edges +| ../lib/os/path.py:4:14:4:14 | externally controlled string | ../lib/os/path.py:5:12:5:12 | externally controlled string | +| ../lib/os/path.py:4:14:4:14 | externally controlled string | ../lib/os/path.py:5:12:5:12 | externally controlled string | +| ../lib/os/path.py:4:14:4:14 | externally controlled string | ../lib/os/path.py:5:12:5:12 | externally controlled string | +| path_injection.py:9:12:9:23 | dict of externally controlled string | path_injection.py:9:12:9:39 | externally controlled string | +| path_injection.py:9:12:9:39 | externally controlled string | path_injection.py:10:40:10:43 | externally controlled string | +| path_injection.py:10:40:10:43 | externally controlled string | path_injection.py:10:14:10:44 | externally controlled string | +| path_injection.py:15:12:15:23 | dict of externally controlled string | path_injection.py:15:12:15:39 | externally controlled string | +| path_injection.py:15:12:15:39 | externally controlled string | path_injection.py:16:56:16:59 | externally controlled string | +| path_injection.py:16:13:16:61 | normalized path | path_injection.py:17:14:17:18 | normalized path | +| path_injection.py:16:30:16:60 | externally controlled string | ../lib/os/path.py:4:14:4:14 | externally controlled string | +| path_injection.py:16:30:16:60 | externally controlled string | path_injection.py:16:13:16:61 | normalized path | +| path_injection.py:16:56:16:59 | externally controlled string | path_injection.py:16:30:16:60 | externally controlled string | +| path_injection.py:24:12:24:23 | dict of externally controlled string | path_injection.py:24:12:24:39 | externally controlled string | +| path_injection.py:24:12:24:39 | externally controlled string | path_injection.py:25:56:25:59 | externally controlled string | +| path_injection.py:25:13:25:61 | normalized path | path_injection.py:26:8:26:12 | normalized path | +| path_injection.py:25:13:25:61 | normalized path | path_injection.py:28:14:28:18 | normalized path | +| path_injection.py:25:30:25:60 | externally controlled string | ../lib/os/path.py:4:14:4:14 | externally controlled string | +| path_injection.py:25:30:25:60 | externally controlled string | path_injection.py:25:13:25:61 | normalized path | +| path_injection.py:25:56:25:59 | externally controlled string | path_injection.py:25:30:25:60 | externally controlled string | +| path_injection.py:33:12:33:23 | dict of externally controlled string | path_injection.py:33:12:33:39 | externally controlled string | +| path_injection.py:33:12:33:39 | externally controlled string | path_injection.py:34:56:34:59 | externally controlled string | +| path_injection.py:34:13:34:61 | normalized path | path_injection.py:35:8:35:12 | normalized path | +| path_injection.py:34:30:34:60 | externally controlled string | ../lib/os/path.py:4:14:4:14 | externally controlled string | +| path_injection.py:34:30:34:60 | externally controlled string | path_injection.py:34:13:34:61 | normalized path | +| path_injection.py:34:56:34:59 | externally controlled string | path_injection.py:34:30:34:60 | externally controlled string | +parents +| ../lib/os/path.py:4:14:4:14 | externally controlled string | path_injection.py:16:30:16:60 | externally controlled string | +| ../lib/os/path.py:4:14:4:14 | externally controlled string | path_injection.py:25:30:25:60 | externally controlled string | +| ../lib/os/path.py:4:14:4:14 | externally controlled string | path_injection.py:34:30:34:60 | externally controlled string | +| ../lib/os/path.py:5:12:5:12 | externally controlled string | path_injection.py:16:30:16:60 | externally controlled string | +| ../lib/os/path.py:5:12:5:12 | externally controlled string | path_injection.py:25:30:25:60 | externally controlled string | +| ../lib/os/path.py:5:12:5:12 | externally controlled string | path_injection.py:34:30:34:60 | externally controlled string | +#select +| path_injection.py:10:14:10:44 | argument to open() | path_injection.py:9:12:9:23 | dict of externally controlled string | path_injection.py:10:14:10:44 | externally controlled string | This path depends on $@. | path_injection.py:9:12:9:23 | flask.request.args | a user-provided value | +| path_injection.py:17:14:17:18 | argument to open() | path_injection.py:15:12:15:23 | dict of externally controlled string | path_injection.py:17:14:17:18 | normalized path | This path depends on $@. | path_injection.py:15:12:15:23 | flask.request.args | a user-provided value | +| path_injection.py:28:14:28:18 | argument to open() | path_injection.py:24:12:24:23 | dict of externally controlled string | path_injection.py:28:14:28:18 | normalized path | This path depends on $@. | path_injection.py:24:12:24:23 | flask.request.args | a user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-022/PathInjection.qlref b/python/ql/test/query-tests/Security/CWE-022/PathInjection.qlref new file mode 100644 index 000000000000..d43482cc509e --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-022/PathInjection.qlref @@ -0,0 +1 @@ +Security/CWE-022/PathInjection.ql diff --git a/python/ql/test/query-tests/Security/CWE-022/options b/python/ql/test/query-tests/Security/CWE-022/options new file mode 100644 index 000000000000..dce78f52325e --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-022/options @@ -0,0 +1,2 @@ +semmle-extractor-options: -p ../lib/ --max-import-depth=3 +optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-022/path_injection.py b/python/ql/test/query-tests/Security/CWE-022/path_injection.py new file mode 100644 index 000000000000..b409e2d02217 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-022/path_injection.py @@ -0,0 +1,36 @@ + +import os.path + +from flask import Flask, request +app = Flask(__name__) + +@app.route("/path1") +def path_injection(): + path = request.args.get('path', '') + f = open(os.path.join(os.getcwd(), path)) + +@app.route("/path2") +def path_injection(): + # Normalized, but not checked + path = request.args.get('path', '') + npath = os.path.normpath(os.path.join(os.getcwd(), path)) + f = open(npath) + +SAFE = "/tmp/scratch_area/" + +@app.route("/path3") +def safe_path(): + # Normalized, but check doesn't reach open(). + path = request.args.get('path', '') + npath = os.path.normpath(os.path.join(os.getcwd(), path)) + if npath.startswith(SAFE): + pass + f = open(npath) + +@app.route("/path4") +def safe_path(): + # Normalized, and checked properly + path = request.args.get('path', '') + npath = os.path.normpath(os.path.join(os.getcwd(), path)) + if npath.startswith(SAFE): + f = open(npath) diff --git a/python/ql/test/query-tests/Security/CWE-078/CommandInjection.expected b/python/ql/test/query-tests/Security/CWE-078/CommandInjection.expected new file mode 100644 index 000000000000..9e0d3008d528 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-078/CommandInjection.expected @@ -0,0 +1,24 @@ +edges +| command_injection.py:10:13:10:24 | dict of externally controlled string | command_injection.py:10:13:10:41 | externally controlled string | +| command_injection.py:10:13:10:41 | externally controlled string | command_injection.py:12:23:12:27 | externally controlled string | +| command_injection.py:12:15:12:27 | externally controlled string | ../lib/os/__init__.py:1:12:1:14 | externally controlled string | +| command_injection.py:12:23:12:27 | externally controlled string | command_injection.py:12:15:12:27 | externally controlled string | +| command_injection.py:17:13:17:24 | dict of externally controlled string | command_injection.py:17:13:17:41 | externally controlled string | +| command_injection.py:17:13:17:41 | externally controlled string | command_injection.py:19:29:19:33 | externally controlled string | +| command_injection.py:19:29:19:33 | externally controlled string | command_injection.py:19:22:19:34 | sequence of externally controlled string | +| command_injection.py:24:11:24:22 | dict of externally controlled string | command_injection.py:24:11:24:37 | externally controlled string | +| command_injection.py:24:11:24:37 | externally controlled string | command_injection.py:25:23:25:25 | externally controlled string | +| command_injection.py:25:23:25:25 | externally controlled string | command_injection.py:25:22:25:36 | first item in sequence of externally controlled string | +| command_injection.py:25:23:25:25 | externally controlled string | command_injection.py:25:22:25:36 | sequence of externally controlled string | +| command_injection.py:30:13:30:24 | dict of externally controlled string | command_injection.py:30:13:30:41 | externally controlled string | +| command_injection.py:30:13:30:41 | externally controlled string | command_injection.py:32:22:32:26 | externally controlled string | +| command_injection.py:32:14:32:26 | externally controlled string | ../lib/os/__init__.py:4:11:4:13 | externally controlled string | +| command_injection.py:32:22:32:26 | externally controlled string | command_injection.py:32:14:32:26 | externally controlled string | +parents +| ../lib/os/__init__.py:1:12:1:14 | externally controlled string | command_injection.py:12:15:12:27 | externally controlled string | +| ../lib/os/__init__.py:4:11:4:13 | externally controlled string | command_injection.py:32:14:32:26 | externally controlled string | +#select +| command_injection.py:12:15:12:27 | shell command | command_injection.py:10:13:10:24 | dict of externally controlled string | command_injection.py:12:15:12:27 | externally controlled string | This command depends on $@. | command_injection.py:10:13:10:24 | flask.request.args | a user-provided value | +| command_injection.py:19:22:19:34 | shell command | command_injection.py:17:13:17:24 | dict of externally controlled string | command_injection.py:19:22:19:34 | sequence of externally controlled string | This command depends on $@. | command_injection.py:17:13:17:24 | flask.request.args | a user-provided value | +| command_injection.py:25:22:25:36 | OS command first argument | command_injection.py:24:11:24:22 | dict of externally controlled string | command_injection.py:25:22:25:36 | first item in sequence of externally controlled string | This command depends on $@. | command_injection.py:24:11:24:22 | flask.request.args | a user-provided value | +| command_injection.py:32:14:32:26 | shell command | command_injection.py:30:13:30:24 | dict of externally controlled string | command_injection.py:32:14:32:26 | externally controlled string | This command depends on $@. | command_injection.py:30:13:30:24 | flask.request.args | a user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-078/CommandInjection.qlref b/python/ql/test/query-tests/Security/CWE-078/CommandInjection.qlref new file mode 100644 index 000000000000..e38b88f29197 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-078/CommandInjection.qlref @@ -0,0 +1 @@ +Security/CWE-078/CommandInjection.ql diff --git a/python/ql/test/query-tests/Security/CWE-078/command_injection.py b/python/ql/test/query-tests/Security/CWE-078/command_injection.py new file mode 100644 index 000000000000..2122a076ec96 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-078/command_injection.py @@ -0,0 +1,32 @@ + +import os +import subprocess + +from flask import Flask, request +app = Flask(__name__) + +@app.route("/command1") +def command_injection1(): + files = request.args.get('files', '') + # Don't let files be `; rm -rf /` + os.system("ls " + files) + + +@app.route("/command2") +def command_injection2(): + files = request.args.get('files', '') + # Don't let files be `; rm -rf /` + subprocess.Popen(["ls", files], shell = True) + + +@app.route("/command3") +def first_arg_injection(): + cmd = request.args.get('cmd', '') + subprocess.Popen([cmd, "param1"]) + + +@app.route("/other_cases") +def others(): + files = request.args.get('files', '') + # Don't let files be `; rm -rf /` + os.popen("ls " + files) diff --git a/python/ql/test/query-tests/Security/CWE-078/options b/python/ql/test/query-tests/Security/CWE-078/options new file mode 100644 index 000000000000..dce78f52325e --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-078/options @@ -0,0 +1,2 @@ +semmle-extractor-options: -p ../lib/ --max-import-depth=3 +optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected new file mode 100644 index 000000000000..b60e97ff4688 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected @@ -0,0 +1,15 @@ +edges +| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | +| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | +| reflected_xss.py:7:18:7:29 | dict of externally controlled string | reflected_xss.py:7:18:7:45 | externally controlled string | +| reflected_xss.py:7:18:7:45 | externally controlled string | reflected_xss.py:8:44:8:53 | externally controlled string | +| reflected_xss.py:8:26:8:53 | externally controlled string | ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | +| reflected_xss.py:8:44:8:53 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | +| reflected_xss.py:12:18:12:29 | dict of externally controlled string | reflected_xss.py:12:18:12:45 | externally controlled string | +| reflected_xss.py:12:18:12:45 | externally controlled string | reflected_xss.py:13:51:13:60 | externally controlled string | +parents +| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | +| ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | +| ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | +#select +| ../lib/flask/__init__.py:16:25:16:26 | flask.response.argument | reflected_xss.py:7:18:7:29 | dict of externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | Cross-site scripting vulnerability due to $@. | reflected_xss.py:7:18:7:29 | flask.request.args | user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.qlref b/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.qlref new file mode 100644 index 000000000000..e0efe1024162 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.qlref @@ -0,0 +1 @@ +Security/CWE-079/ReflectedXss.ql diff --git a/python/ql/test/query-tests/Security/CWE-079/options b/python/ql/test/query-tests/Security/CWE-079/options new file mode 100644 index 000000000000..492768b3481f --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-079/options @@ -0,0 +1 @@ +semmle-extractor-options: -p ../lib/ --max-import-depth=3 diff --git a/python/ql/test/query-tests/Security/CWE-079/reflected_xss.py b/python/ql/test/query-tests/Security/CWE-079/reflected_xss.py new file mode 100644 index 000000000000..fecb84ef7252 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-079/reflected_xss.py @@ -0,0 +1,18 @@ +from flask import Flask, request, make_response, escape + +app = Flask(__name__) + +@app.route('/unsafe') +def unsafe(): + first_name = request.args.get('name', '') + return make_response("Your name is " + first_name) + +@app.route('/safe') +def safe(): + first_name = request.args.get('name', '') + return make_response("Your name is " + escape(first_name)) + +urlpatterns = [ + url(r'^r1$', response_unsafe, name='response-unsafe'), + url(r'^r2$', response_safe, name='response-safe') +] diff --git a/python/ql/test/query-tests/Security/CWE-089/SqlInjection.expected b/python/ql/test/query-tests/Security/CWE-089/SqlInjection.expected new file mode 100644 index 000000000000..1555b7cd7793 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-089/SqlInjection.expected @@ -0,0 +1,25 @@ +edges +| sql_injection.py:9:15:9:21 | django.request.HttpRequest | sql_injection.py:11:8:11:14 | django.request.HttpRequest | +| sql_injection.py:9:15:9:21 | django.request.HttpRequest | sql_injection.py:12:16:12:22 | django.request.HttpRequest | +| sql_injection.py:12:16:12:22 | django.request.HttpRequest | sql_injection.py:12:16:12:27 | django.http.request.QueryDict | +| sql_injection.py:12:16:12:27 | django.http.request.QueryDict | sql_injection.py:12:16:12:39 | externally controlled string | +| sql_injection.py:12:16:12:39 | externally controlled string | sql_injection.py:16:62:16:65 | externally controlled string | +| sql_injection.py:12:16:12:39 | externally controlled string | sql_injection.py:19:63:19:66 | externally controlled string | +| sql_injection.py:12:16:12:39 | externally controlled string | sql_injection.py:22:88:22:91 | externally controlled string | +| sql_injection.py:12:16:12:39 | externally controlled string | sql_injection.py:23:76:23:79 | externally controlled string | +| sql_injection.py:12:16:12:39 | externally controlled string | sql_injection.py:24:78:24:81 | externally controlled string | +| sql_injection.py:13:16:13:34 | django.db.connection.cursor | sql_injection.py:15:9:15:12 | django.db.connection.cursor | +| sql_injection.py:13:16:13:34 | django.db.connection.cursor | sql_injection.py:18:9:18:12 | django.db.connection.cursor | +| sql_injection.py:19:63:19:66 | externally controlled string | sql_injection.py:19:13:19:66 | externally controlled string | +| sql_injection.py:22:9:22:20 | django.db.models.Model.objects | sql_injection.py:22:9:22:93 | django.db.models.Model.objects | +| sql_injection.py:22:88:22:91 | externally controlled string | sql_injection.py:22:38:22:91 | externally controlled string | +| sql_injection.py:23:9:23:20 | django.db.models.Model.objects | sql_injection.py:23:9:23:80 | django.db.models.Model.objects | +| sql_injection.py:23:76:23:79 | externally controlled string | sql_injection.py:23:26:23:79 | externally controlled string | +| sql_injection.py:24:9:24:20 | django.db.models.Model.objects | sql_injection.py:24:9:24:82 | django.db.models.Model.objects | +| sql_injection.py:24:78:24:81 | externally controlled string | sql_injection.py:24:28:24:81 | externally controlled string | +parents +#select +| sql_injection.py:19:13:19:66 | db.connection.execute | sql_injection.py:9:15:9:21 | django.request.HttpRequest | sql_injection.py:19:13:19:66 | externally controlled string | This SQL query depends on $@. | sql_injection.py:9:15:9:21 | Django request source | a user-provided value | +| sql_injection.py:22:38:22:91 | django.db.models.expressions.RawSQL(sink,...) | sql_injection.py:9:15:9:21 | django.request.HttpRequest | sql_injection.py:22:38:22:91 | externally controlled string | This SQL query depends on $@. | sql_injection.py:9:15:9:21 | Django request source | a user-provided value | +| sql_injection.py:23:26:23:79 | django.models.QuerySet.raw(sink,...) | sql_injection.py:9:15:9:21 | django.request.HttpRequest | sql_injection.py:23:26:23:79 | externally controlled string | This SQL query depends on $@. | sql_injection.py:9:15:9:21 | Django request source | a user-provided value | +| sql_injection.py:24:28:24:81 | django.models.QuerySet.extra(sink,...) | sql_injection.py:9:15:9:21 | django.request.HttpRequest | sql_injection.py:24:28:24:81 | externally controlled string | This SQL query depends on $@. | sql_injection.py:9:15:9:21 | Django request source | a user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-089/SqlInjection.qlref b/python/ql/test/query-tests/Security/CWE-089/SqlInjection.qlref new file mode 100644 index 000000000000..d1d02cbe8d37 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-089/SqlInjection.qlref @@ -0,0 +1 @@ +Security/CWE-089/SqlInjection.ql diff --git a/python/ql/test/query-tests/Security/CWE-089/options b/python/ql/test/query-tests/Security/CWE-089/options new file mode 100644 index 000000000000..492768b3481f --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-089/options @@ -0,0 +1 @@ +semmle-extractor-options: -p ../lib/ --max-import-depth=3 diff --git a/python/ql/test/query-tests/Security/CWE-089/sql_injection.py b/python/ql/test/query-tests/Security/CWE-089/sql_injection.py new file mode 100644 index 000000000000..b312241b8099 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-089/sql_injection.py @@ -0,0 +1,28 @@ + +from django.conf.urls import patterns, url +from django.db import connection, models +from django.db.models.expressions import RawSQL + +class Name(models.Model): + pass + +def save_name(request): + + if request.method == 'POST': + name = request.POST.get('name') + curs = connection.cursor() + #GOOD -- Using parameters + curs.execute( + "insert into names_file ('name') values ('%s')", name) + #BAD -- Using string formatting + curs.execute( + "insert into names_file ('name') values ('%s')" % name) + + #BAD -- other ways of executing raw SQL code with string interpolation + Name.objects.annotate(RawSQL("insert into names_file ('name') values ('%s')" % name)) + Name.objects.raw("insert into names_file ('name') values ('%s')" % name) + Name.objects.extra("insert into names_file ('name') values ('%s')" % name) + +urlpatterns = patterns(url(r'^save_name/$', + save_name, name='save_name')) + diff --git a/python/ql/test/query-tests/Security/CWE-094/CodeInjection.expected b/python/ql/test/query-tests/Security/CWE-094/CodeInjection.expected new file mode 100644 index 000000000000..8c621808c14d --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-094/CodeInjection.expected @@ -0,0 +1,12 @@ +edges +| code_injection.py:4:20:4:26 | django.request.HttpRequest | code_injection.py:5:8:5:14 | django.request.HttpRequest | +| code_injection.py:4:20:4:26 | django.request.HttpRequest | code_injection.py:6:22:6:28 | django.request.HttpRequest | +| code_injection.py:6:22:6:28 | django.request.HttpRequest | code_injection.py:6:22:6:33 | django.http.request.QueryDict | +| code_injection.py:6:22:6:33 | django.http.request.QueryDict | code_injection.py:6:22:6:55 | externally controlled string | +| code_injection.py:6:22:6:55 | externally controlled string | code_injection.py:7:34:7:43 | externally controlled string | +| code_injection.py:7:34:7:43 | externally controlled string | ../lib/base64.py:1:18:1:18 | externally controlled string | +| code_injection.py:7:34:7:43 | externally controlled string | code_injection.py:7:14:7:44 | externally controlled string | +parents +| ../lib/base64.py:1:18:1:18 | externally controlled string | code_injection.py:7:34:7:43 | externally controlled string | +#select +| code_injection.py:7:14:7:44 | exec or eval | code_injection.py:4:20:4:26 | django.request.HttpRequest | code_injection.py:7:14:7:44 | externally controlled string | $@ flows to here and is interpreted as code. | code_injection.py:4:20:4:26 | Django request source | User-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-094/CodeInjection.qlref b/python/ql/test/query-tests/Security/CWE-094/CodeInjection.qlref new file mode 100644 index 000000000000..fe9adbf3b64d --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-094/CodeInjection.qlref @@ -0,0 +1 @@ +Security/CWE-094/CodeInjection.ql diff --git a/python/ql/test/query-tests/Security/CWE-094/code_injection.py b/python/ql/test/query-tests/Security/CWE-094/code_injection.py new file mode 100644 index 000000000000..2633f55cbdb6 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-094/code_injection.py @@ -0,0 +1,12 @@ +from django.conf.urls import url +import base64 + +def code_execution(request): + if request.method == 'POST': + first_name = request.POST.get('first_name', '') + exec(base64.decodestring(first_name)) + +urlpatterns = [ + # Route to code_execution + url(r'^code-ex$', code_execution, name='code-execution') +] diff --git a/python/ql/test/query-tests/Security/CWE-094/options b/python/ql/test/query-tests/Security/CWE-094/options new file mode 100644 index 000000000000..dce78f52325e --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-094/options @@ -0,0 +1,2 @@ +semmle-extractor-options: -p ../lib/ --max-import-depth=3 +optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-209/StackTraceExposure.expected b/python/ql/test/query-tests/Security/CWE-209/StackTraceExposure.expected new file mode 100644 index 000000000000..cbdfa9c8d462 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-209/StackTraceExposure.expected @@ -0,0 +1,13 @@ +edges +| test.py:33:15:33:36 | exception info | test.py:34:29:34:31 | exception info | +| test.py:34:29:34:31 | exception info | test.py:36:18:36:20 | exception info | +| test.py:36:18:36:20 | exception info | test.py:37:25:37:27 | exception info | +| test.py:37:12:37:27 | exception info | test.py:34:16:34:32 | exception info | +| test.py:37:25:37:27 | exception info | test.py:37:12:37:27 | exception info | +parents +| test.py:36:18:36:20 | exception info | test.py:34:29:34:31 | exception info | +| test.py:37:12:37:27 | exception info | test.py:34:29:34:31 | exception info | +| test.py:37:25:37:27 | exception info | test.py:34:29:34:31 | exception info | +#select +| test.py:16:16:16:37 | flask.routed.response | test.py:16:16:16:37 | exception info | test.py:16:16:16:37 | exception info | $@ may be exposed to an external user | test.py:16:16:16:37 | exception.info.source | Error information | +| test.py:34:16:34:32 | flask.routed.response | test.py:33:15:33:36 | exception info | test.py:34:16:34:32 | exception info | $@ may be exposed to an external user | test.py:33:15:33:36 | exception.info.source | Error information | diff --git a/python/ql/test/query-tests/Security/CWE-209/StackTraceExposure.qlref b/python/ql/test/query-tests/Security/CWE-209/StackTraceExposure.qlref new file mode 100644 index 000000000000..18cf2d49a1aa --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-209/StackTraceExposure.qlref @@ -0,0 +1 @@ +Security/CWE-209/StackTraceExposure.ql diff --git a/python/ql/test/query-tests/Security/CWE-209/options b/python/ql/test/query-tests/Security/CWE-209/options new file mode 100644 index 000000000000..2729d5a143ad --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-209/options @@ -0,0 +1 @@ +semmle-extractor-options: -p ../lib/ --max-import-depth=2 diff --git a/python/ql/test/query-tests/Security/CWE-209/test.py b/python/ql/test/query-tests/Security/CWE-209/test.py new file mode 100644 index 000000000000..6e96cba15c57 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-209/test.py @@ -0,0 +1,37 @@ +from flask import Flask +app = Flask(__name__) + + +import traceback + +def do_computation(): + raise Exception("Secret info") + +# BAD +@app.route('/bad') +def server_bad(): + try: + do_computation() + except Exception as e: + return traceback.format_exc() + +# GOOD +@app.route('/good') +def server_good(): + try: + do_computation() + except Exception as e: + log(traceback.format_exc()) + return "An internal error has occurred!" + +#BAD +@app.route('/bad/with-flow') +def server_bad_flow(): + try: + do_computation() + except Exception as e: + err = traceback.format_exc() + return format_error(err) + +def format_error(msg): + return "[ERROR] " + msg diff --git a/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.expected b/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.expected new file mode 100644 index 000000000000..3a23ea54ee29 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.expected @@ -0,0 +1,3 @@ +| test.py:10:1:10:19 | ControlFlowNode for Attribute() | A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger. | +| test.py:25:1:25:20 | ControlFlowNode for Attribute() | A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger. | +| test.py:29:1:29:20 | ControlFlowNode for Attribute() | A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger. | diff --git a/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.qlref b/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.qlref new file mode 100644 index 000000000000..0e21a3ac14fe --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.qlref @@ -0,0 +1 @@ +Security/CWE-215/FlaskDebug.ql diff --git a/python/ql/test/query-tests/Security/CWE-215/options b/python/ql/test/query-tests/Security/CWE-215/options new file mode 100644 index 000000000000..84717fe64cff --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-215/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=2 -p ../lib diff --git a/python/ql/test/query-tests/Security/CWE-215/test.py b/python/ql/test/query-tests/Security/CWE-215/test.py new file mode 100644 index 000000000000..6054caf1d33b --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-215/test.py @@ -0,0 +1,37 @@ +from flask import Flask + +app = Flask(__name__) + +@app.route('/crash') +def main(): + raise Exception() + +# bad +app.run(debug=True) + +# okay +app.run() +app.run(debug=False) + +# also okay +run(debug=True) + +app.notrun(debug=True) + +# a slightly more involved example using flow and truthy values + +DEBUG = True + +app.run(debug=DEBUG) + +DEBUG = 1 + +app.run(debug=DEBUG) + +if False: + app.run(debug=True) + +# false negative + +runapp = app.run +runapp(debug=True) diff --git a/python/ql/test/query-tests/Security/CWE-295/RequestWithoutValidation.expected b/python/ql/test/query-tests/Security/CWE-295/RequestWithoutValidation.expected new file mode 100644 index 000000000000..5406d5a9b0bf --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-295/RequestWithoutValidation.expected @@ -0,0 +1,5 @@ +| make_request.py:5:1:5:48 | ControlFlowNode for Attribute() | Call to $@ with verify=$@ | ../lib/requests.py:2:1:2:36 | Function get | requests.get | make_request.py:5:43:5:47 | ControlFlowNode for False | False | +| make_request.py:7:1:7:49 | ControlFlowNode for Attribute() | Call to $@ with verify=$@ | ../lib/requests.py:11:1:11:46 | Function post | requests.post | make_request.py:7:44:7:48 | ControlFlowNode for False | False | +| make_request.py:12:1:12:39 | ControlFlowNode for put() | Call to $@ with verify=$@ | ../lib/requests.py:14:1:14:34 | Function put | requests.put | make_request.py:12:34:12:38 | ControlFlowNode for False | False | +| make_request.py:28:5:28:46 | ControlFlowNode for patch() | Call to $@ with verify=$@ | ../lib/requests.py:17:1:17:36 | Function patch | requests.patch | make_request.py:30:6:30:10 | ControlFlowNode for False | False | +| make_request.py:34:1:34:45 | ControlFlowNode for Attribute() | Call to $@ with verify=$@ | ../lib/requests.py:11:1:11:46 | Function post | requests.post | make_request.py:34:44:34:44 | ControlFlowNode for IntegerLiteral | False | diff --git a/python/ql/test/query-tests/Security/CWE-295/RequestWithoutValidation.qlref b/python/ql/test/query-tests/Security/CWE-295/RequestWithoutValidation.qlref new file mode 100644 index 000000000000..7ad4d4d2ae34 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-295/RequestWithoutValidation.qlref @@ -0,0 +1 @@ +Security/CWE-295/RequestWithoutValidation.ql diff --git a/python/ql/test/query-tests/Security/CWE-295/make_request.py b/python/ql/test/query-tests/Security/CWE-295/make_request.py new file mode 100644 index 000000000000..9d732713dbf6 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-295/make_request.py @@ -0,0 +1,34 @@ +import requests + +#Simple cases +requests.get('https://semmle.com', verify=True) # GOOD +requests.get('https://semmle.com', verify=False) # BAD +requests.post('https://semmle.com', verify=True) # GOOD +requests.post('https://semmle.com', verify=False) # BAD + +# Simple flow +put = requests.put +put('https://semmle.com', verify="/path/to/cert/") # GOOD +put('https://semmle.com', verify=False) # BAD + +#Other flow +delete = requests.delete + +def req1(verify=False): + delete('https://semmle.com', verify) # BAD + if verify: + delete('https://semmle.com', verify) # GOOD + if not verify: + return + delete('https://semmle.com', verify) # GOOD + +patch = requests.patch + +def req2(verify): + patch('https://semmle.com', verify=verify) # BAD (from line 30) + +req2(False) # BAD (at line 28) +req2("/path/to/cert/") # GOOD + +#Falsey value +requests.post('https://semmle.com', verify=0) # BAD diff --git a/python/ql/test/query-tests/Security/CWE-295/options b/python/ql/test/query-tests/Security/CWE-295/options new file mode 100644 index 000000000000..492768b3481f --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-295/options @@ -0,0 +1 @@ +semmle-extractor-options: -p ../lib/ --max-import-depth=3 diff --git a/python/ql/test/query-tests/Security/CWE-327/BrokenCryptoAlgorithm.expected b/python/ql/test/query-tests/Security/CWE-327/BrokenCryptoAlgorithm.expected new file mode 100644 index 000000000000..9fb4ed3ed5db --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-327/BrokenCryptoAlgorithm.expected @@ -0,0 +1,2 @@ +| test_cryptography.py:7:29:7:37 | Use of weak crypto algorithm | Sensitive data from $@ is used in a broken or weak cryptographic algorithm. | test_cryptography.py:4:17:4:30 | sensitive.data.source | sensitive.data.source | +| test_pycrypto.py:6:27:6:35 | Use of weak crypto algorithm ARC4 | Sensitive data from $@ is used in a broken or weak cryptographic algorithm. | test_pycrypto.py:4:17:4:30 | sensitive.data.source | sensitive.data.source | diff --git a/python/ql/test/query-tests/Security/CWE-327/BrokenCryptoAlgorithm.qlref b/python/ql/test/query-tests/Security/CWE-327/BrokenCryptoAlgorithm.qlref new file mode 100644 index 000000000000..3f7aff53700d --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-327/BrokenCryptoAlgorithm.qlref @@ -0,0 +1 @@ +Security/CWE-327/BrokenCryptoAlgorithm.ql diff --git a/python/ql/test/query-tests/Security/CWE-327/TestNode.expected b/python/ql/test/query-tests/Security/CWE-327/TestNode.expected new file mode 100644 index 000000000000..0a184314a93a --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-327/TestNode.expected @@ -0,0 +1,14 @@ +| Taint Crypto.Cipher.ARC4 | test_pycrypto.py:5:14:5:27 | test_pycrypto.py:5 | test_pycrypto.py:5:14:5:27 | Attribute() | | +| Taint Crypto.Cipher.ARC4 | test_pycrypto.py:6:12:6:17 | test_pycrypto.py:6 | test_pycrypto.py:6:12:6:17 | cipher | | +| Taint cryptography.Cipher.RC4 | test_cryptography.py:5:14:5:47 | test_cryptography.py:5 | test_cryptography.py:5:14:5:47 | Cipher() | | +| Taint cryptography.Cipher.RC4 | test_cryptography.py:6:17:6:22 | test_cryptography.py:6 | test_cryptography.py:6:17:6:22 | cipher | | +| Taint cryptography.encryptor.RC4 | test_cryptography.py:6:17:6:34 | test_cryptography.py:6 | test_cryptography.py:6:17:6:34 | Attribute() | | +| Taint cryptography.encryptor.RC4 | test_cryptography.py:7:12:7:20 | test_cryptography.py:7 | test_cryptography.py:7:12:7:20 | encryptor | | +| Taint cryptography.encryptor.RC4 | test_cryptography.py:7:42:7:50 | test_cryptography.py:7 | test_cryptography.py:7:42:7:50 | encryptor | | +| Taint sensitive.data | test_cryptography.py:4:17:4:28 | test_cryptography.py:4 | test_cryptography.py:4:17:4:28 | get_password | | +| Taint sensitive.data | test_cryptography.py:4:17:4:30 | test_cryptography.py:4 | test_cryptography.py:4:17:4:30 | get_password() | | +| Taint sensitive.data | test_cryptography.py:7:29:7:37 | test_cryptography.py:7 | test_cryptography.py:7:29:7:37 | dangerous | | +| Taint sensitive.data | test_cryptography.py:7:42:7:50 | test_cryptography.py:7 | test_cryptography.py:7:42:7:50 | encryptor | | +| Taint sensitive.data | test_pycrypto.py:4:17:4:28 | test_pycrypto.py:4 | test_pycrypto.py:4:17:4:28 | get_password | | +| Taint sensitive.data | test_pycrypto.py:4:17:4:30 | test_pycrypto.py:4 | test_pycrypto.py:4:17:4:30 | get_password() | | +| Taint sensitive.data | test_pycrypto.py:6:27:6:35 | test_pycrypto.py:6 | test_pycrypto.py:6:27:6:35 | dangerous | | diff --git a/python/ql/test/query-tests/Security/CWE-327/TestNode.ql b/python/ql/test/query-tests/Security/CWE-327/TestNode.ql new file mode 100644 index 000000000000..71ec310dd39d --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-327/TestNode.ql @@ -0,0 +1,9 @@ +import python +import semmle.python.security.TaintTracking + +import python +import semmle.python.security.SensitiveData +import semmle.python.security.Crypto + +from TaintedNode n +select n.getTrackedValue(), n.getLocation(), n.getNode().getNode(), n.getContext() diff --git a/python/ql/test/query-tests/Security/CWE-327/options b/python/ql/test/query-tests/Security/CWE-327/options new file mode 100644 index 000000000000..492768b3481f --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-327/options @@ -0,0 +1 @@ +semmle-extractor-options: -p ../lib/ --max-import-depth=3 diff --git a/python/ql/test/query-tests/Security/CWE-327/test_cryptography.py b/python/ql/test/query-tests/Security/CWE-327/test_cryptography.py new file mode 100644 index 000000000000..37baa1d1a805 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-327/test_cryptography.py @@ -0,0 +1,8 @@ +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms + +def get_badly_encrypted_password(): + dangerous = get_password() + cipher = Cipher(algorithms.ARC4(key), _, _) + encryptor = cipher.encryptor() + return encryptor.update(dangerous) + encryptor.finalize() + diff --git a/python/ql/test/query-tests/Security/CWE-327/test_pycrypto.py b/python/ql/test/query-tests/Security/CWE-327/test_pycrypto.py new file mode 100644 index 000000000000..49f910f1af8b --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-327/test_pycrypto.py @@ -0,0 +1,7 @@ +from Crypto.Cipher import ARC4 + +def get_badly_encrypted_password(): + dangerous = get_password() + cipher = ARC4.new(_, _) + return cipher.encrypt(dangerous) + diff --git a/python/ql/test/query-tests/Security/CWE-502/UnsafeDeserialization.expected b/python/ql/test/query-tests/Security/CWE-502/UnsafeDeserialization.expected new file mode 100644 index 000000000000..79dee42edb4e --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-502/UnsafeDeserialization.expected @@ -0,0 +1,12 @@ +edges +| test.py:11:15:11:26 | dict of externally controlled string | test.py:11:15:11:41 | externally controlled string | +| test.py:11:15:11:41 | externally controlled string | test.py:12:18:12:24 | externally controlled string | +| test.py:11:15:11:41 | externally controlled string | test.py:13:15:13:21 | externally controlled string | +| test.py:11:15:11:41 | externally controlled string | test.py:14:19:14:25 | externally controlled string | +| test.py:13:15:13:21 | externally controlled string | ../lib/yaml.py:1:10:1:10 | externally controlled string | +parents +| ../lib/yaml.py:1:10:1:10 | externally controlled string | test.py:13:15:13:21 | externally controlled string | +#select +| test.py:12:18:12:24 | unpickling untrusted data | test.py:11:15:11:26 | dict of externally controlled string | test.py:12:18:12:24 | externally controlled string | Deserializing of $@. | test.py:11:15:11:26 | flask.request.args | untrusted input | +| test.py:13:15:13:21 | yaml.load vulnerability | test.py:11:15:11:26 | dict of externally controlled string | test.py:13:15:13:21 | externally controlled string | Deserializing of $@. | test.py:11:15:11:26 | flask.request.args | untrusted input | +| test.py:14:19:14:25 | unmarshaling vulnerability | test.py:11:15:11:26 | dict of externally controlled string | test.py:14:19:14:25 | externally controlled string | Deserializing of $@. | test.py:11:15:11:26 | flask.request.args | untrusted input | diff --git a/python/ql/test/query-tests/Security/CWE-502/UnsafeDeserialization.qlref b/python/ql/test/query-tests/Security/CWE-502/UnsafeDeserialization.qlref new file mode 100644 index 000000000000..fa9c0ceb3cb8 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-502/UnsafeDeserialization.qlref @@ -0,0 +1 @@ +Security/CWE-502/UnsafeDeserialization.ql diff --git a/python/ql/test/query-tests/Security/CWE-502/options b/python/ql/test/query-tests/Security/CWE-502/options new file mode 100644 index 000000000000..b63c517ee296 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-502/options @@ -0,0 +1,2 @@ +semmle-extractor-options: --max-import-depth=2 -p ../lib +optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-502/test.py b/python/ql/test/query-tests/Security/CWE-502/test.py new file mode 100644 index 000000000000..27a003136c0c --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-502/test.py @@ -0,0 +1,16 @@ +import flask +import pickle +import yaml +import marshal + +from flask import Flask, request +app = Flask(__name__) + +@app.route("/") +def hello(): + payload = request.args.get('payload') + pickle.loads(payload) + yaml.load(payload) + marshal.loads(payload) + + diff --git a/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected new file mode 100644 index 000000000000..5de71ec5ee56 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected @@ -0,0 +1,10 @@ +edges +| test.py:7:22:7:33 | dict of externally controlled string | test.py:7:22:7:51 | externally controlled string | +| test.py:7:22:7:51 | externally controlled string | test.py:8:21:8:26 | externally controlled string | +| test.py:8:21:8:26 | externally controlled string | ../lib/flask/__init__.py:11:14:11:21 | externally controlled string | +| test.py:15:17:15:28 | dict of externally controlled string | test.py:15:17:15:42 | externally controlled string | +| test.py:15:17:15:42 | externally controlled string | test.py:17:13:17:21 | externally controlled string | +parents +| ../lib/flask/__init__.py:11:14:11:21 | externally controlled string | test.py:8:21:8:26 | externally controlled string | +#select +| test.py:8:21:8:26 | flask.redirect | test.py:7:22:7:33 | dict of externally controlled string | test.py:8:21:8:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:7:22:7:33 | flask.request.args | a user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.qlref b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.qlref new file mode 100644 index 000000000000..8b63d80f0db2 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.qlref @@ -0,0 +1,2 @@ +Security/CWE-601/UrlRedirect.ql + diff --git a/python/ql/test/query-tests/Security/CWE-601/options b/python/ql/test/query-tests/Security/CWE-601/options new file mode 100644 index 000000000000..84717fe64cff --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-601/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=2 -p ../lib diff --git a/python/ql/test/query-tests/Security/CWE-601/test.py b/python/ql/test/query-tests/Security/CWE-601/test.py new file mode 100644 index 000000000000..34ad352db0e3 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-601/test.py @@ -0,0 +1,18 @@ +from flask import Flask, request, redirect + +app = Flask(__name__) + +@app.route('/') +def hello(): + target = files = request.args.get('target', '') + return redirect(target, code=302) + + +#Check for safe prefixes + +@app.route('/ok') +def ok(): + untrusted = request.args.get('ok', '') + safe = "safe" + safe += untrusted + return redirect(safe, code=302) diff --git a/python/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.expected b/python/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.expected new file mode 100644 index 000000000000..d90e739799bf --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.expected @@ -0,0 +1,2 @@ +| test.py:14:18:14:25 | Taint sink | Use of hardcoded credentials from $@. | test.py:5:12:5:24 | Taint source | Taint source | +| test.py:15:18:15:25 | Taint sink | Use of hardcoded credentials from $@. | test.py:6:12:6:25 | Taint source | Taint source | diff --git a/python/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.qlref b/python/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.qlref new file mode 100644 index 000000000000..0d0fafa271c0 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.qlref @@ -0,0 +1 @@ +Security/CWE-798/HardcodedCredentials.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Security/CWE-798/test.py b/python/ql/test/query-tests/Security/CWE-798/test.py new file mode 100644 index 000000000000..bcfb1feee4c3 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-798/test.py @@ -0,0 +1,19 @@ + + +HOST = "acme-trading.com" +PORT = 8000 +USERNAME = "road_runner" +PASSWORD = "insecure_pwd" + + +def sell(client, units): + + conn = client.connect( + host=HOST, + port=PORT, + username=USERNAME, + password=PASSWORD) + + conn.cmd("sell", 1000) + conn.close() + diff --git a/python/ql/test/query-tests/Security/lib/Crypto/Cipher/AES.py b/python/ql/test/query-tests/Security/lib/Crypto/Cipher/AES.py new file mode 100644 index 000000000000..bcab58405ead --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/Crypto/Cipher/AES.py @@ -0,0 +1,3 @@ + +def new(*args): + pass diff --git a/python/ql/test/query-tests/Security/lib/Crypto/Cipher/ARC4.py b/python/ql/test/query-tests/Security/lib/Crypto/Cipher/ARC4.py new file mode 100644 index 000000000000..bcab58405ead --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/Crypto/Cipher/ARC4.py @@ -0,0 +1,3 @@ + +def new(*args): + pass diff --git a/python/ql/test/query-tests/Security/lib/Crypto/Cipher/__init__.py b/python/ql/test/query-tests/Security/lib/Crypto/Cipher/__init__.py new file mode 100644 index 000000000000..ab22f65be6e3 --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/Crypto/Cipher/__init__.py @@ -0,0 +1 @@ +__all__ = ['AES', 'ARC4'] diff --git a/python/ql/test/query-tests/Security/lib/Crypto/__init__.py b/python/ql/test/query-tests/Security/lib/Crypto/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/lib/base64.py b/python/ql/test/query-tests/Security/lib/base64.py new file mode 100644 index 000000000000..a2b97ca63acc --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/base64.py @@ -0,0 +1,2 @@ +def decodestring(s): + return None diff --git a/python/ql/test/query-tests/Security/lib/cryptography/__init__.py b/python/ql/test/query-tests/Security/lib/cryptography/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/lib/cryptography/hazmat/__init__.py b/python/ql/test/query-tests/Security/lib/cryptography/hazmat/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/lib/cryptography/hazmat/primitives/__init__.py b/python/ql/test/query-tests/Security/lib/cryptography/hazmat/primitives/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/lib/cryptography/hazmat/primitives/ciphers/__init__.py b/python/ql/test/query-tests/Security/lib/cryptography/hazmat/primitives/ciphers/__init__.py new file mode 100644 index 000000000000..f37f14e88f8f --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/cryptography/hazmat/primitives/ciphers/__init__.py @@ -0,0 +1,3 @@ + +class Cipher(object): + pass diff --git a/python/ql/test/query-tests/Security/lib/cryptography/hazmat/primitives/ciphers/algorithms.py b/python/ql/test/query-tests/Security/lib/cryptography/hazmat/primitives/ciphers/algorithms.py new file mode 100644 index 000000000000..e423804e4f5c --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -0,0 +1,3 @@ + +class ARC4(object): + name = "RC4" diff --git a/python/ql/test/query-tests/Security/lib/django/__init__.py b/python/ql/test/query-tests/Security/lib/django/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/lib/django/conf/__init__.py b/python/ql/test/query-tests/Security/lib/django/conf/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/lib/django/conf/urls.py b/python/ql/test/query-tests/Security/lib/django/conf/urls.py new file mode 100644 index 000000000000..cf65525e893c --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/django/conf/urls.py @@ -0,0 +1,7 @@ + +def url(pattern, *args): + pass + +def patterns(*urls): + pass + diff --git a/python/ql/test/query-tests/Security/lib/django/db/__init__.py b/python/ql/test/query-tests/Security/lib/django/db/__init__.py new file mode 100644 index 000000000000..3e291c5731d6 --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/django/db/__init__.py @@ -0,0 +1 @@ +connection = object() diff --git a/python/ql/test/query-tests/Security/lib/django/db/models/__init__.py b/python/ql/test/query-tests/Security/lib/django/db/models/__init__.py new file mode 100644 index 000000000000..eb9c72adc45b --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/django/db/models/__init__.py @@ -0,0 +1,2 @@ +class Model: + pass diff --git a/python/ql/test/query-tests/Security/lib/django/db/models/expressions.py b/python/ql/test/query-tests/Security/lib/django/db/models/expressions.py new file mode 100644 index 000000000000..d7e0d1c27b6c --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/django/db/models/expressions.py @@ -0,0 +1,2 @@ +class RawSQL: + pass diff --git a/python/ql/test/query-tests/Security/lib/flask/__init__.py b/python/ql/test/query-tests/Security/lib/flask/__init__.py new file mode 100644 index 000000000000..9277dc335639 --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/flask/__init__.py @@ -0,0 +1,20 @@ + + +class Flask(object): + def run(self, *args, **kwargs): pass + +from .globals import request + +class Response(object): + pass + +def redirect(location, code=302, Response=None): + pass + +def make_response(rv): + if isinstance(rv, str): + return Response(rv) + elif isinstance(rv, Response): + return rv + else: + pass diff --git a/python/ql/test/query-tests/Security/lib/flask/globals.py b/python/ql/test/query-tests/Security/lib/flask/globals.py new file mode 100644 index 000000000000..ef717051f79f --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/flask/globals.py @@ -0,0 +1,5 @@ + +class LocalProxy(object): + pass + +request = LocalProxy() diff --git a/python/ql/test/query-tests/Security/lib/flask/urls.py b/python/ql/test/query-tests/Security/lib/flask/urls.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/lib/flask/views.py b/python/ql/test/query-tests/Security/lib/flask/views.py new file mode 100644 index 000000000000..2cd20261dad1 --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/flask/views.py @@ -0,0 +1,11 @@ + + + +class View(object): + pass + + +class MethodView(object): + pass + + diff --git a/python/ql/test/query-tests/Security/lib/marshall.py b/python/ql/test/query-tests/Security/lib/marshall.py new file mode 100644 index 000000000000..410fa21087e5 --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/marshall.py @@ -0,0 +1,2 @@ +def loads(*args, **kwargs): + return None diff --git a/python/ql/test/query-tests/Security/lib/os/__init__.py b/python/ql/test/query-tests/Security/lib/os/__init__.py new file mode 100644 index 000000000000..9cbc1f155597 --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/os/__init__.py @@ -0,0 +1,5 @@ +def system(cmd, *args, **kwargs): + return None + +def popen(cmd, *args, **kwargs): + return None diff --git a/python/ql/test/query-tests/Security/lib/os/path.py b/python/ql/test/query-tests/Security/lib/os/path.py new file mode 100644 index 000000000000..d54092e80b0d --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/os/path.py @@ -0,0 +1,5 @@ +def join(a, *b): + return a + "/" + "/".join(b) + +def normpath(x): + return x diff --git a/python/ql/test/query-tests/Security/lib/pickle.py b/python/ql/test/query-tests/Security/lib/pickle.py new file mode 100644 index 000000000000..410fa21087e5 --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/pickle.py @@ -0,0 +1,2 @@ +def loads(*args, **kwargs): + return None diff --git a/python/ql/test/query-tests/Security/lib/requests.py b/python/ql/test/query-tests/Security/lib/requests.py new file mode 100644 index 000000000000..01afdbb12085 --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/requests.py @@ -0,0 +1,21 @@ + +def get(url, params=None, **kwargs): + pass + +def options(url, **kwargs): + pass + +def head(url, **kwargs): + pass + +def post(url, data=None, json=None, **kwargs): + pass + +def put(url, data=None, **kwargs): + pass + +def patch(url, data=None, **kwargs): + pass + +def delete(url, **kwargs): + pass diff --git a/python/ql/test/query-tests/Security/lib/subprocess.py b/python/ql/test/query-tests/Security/lib/subprocess.py new file mode 100644 index 000000000000..efb2ba183f09 --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/subprocess.py @@ -0,0 +1,2 @@ +def Popen(*args, **kwargs): + return None \ No newline at end of file diff --git a/python/ql/test/query-tests/Security/lib/traceback.py b/python/ql/test/query-tests/Security/lib/traceback.py new file mode 100644 index 000000000000..2a7c5e58847a --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/traceback.py @@ -0,0 +1,2 @@ +def format_exc(): + return None \ No newline at end of file diff --git a/python/ql/test/query-tests/Security/lib/yaml.py b/python/ql/test/query-tests/Security/lib/yaml.py new file mode 100644 index 000000000000..51fe2fd1d42c --- /dev/null +++ b/python/ql/test/query-tests/Security/lib/yaml.py @@ -0,0 +1,2 @@ +def load(s): + pass diff --git a/python/ql/test/query-tests/Security/options b/python/ql/test/query-tests/Security/options new file mode 100644 index 000000000000..58ad829f5a88 --- /dev/null +++ b/python/ql/test/query-tests/Security/options @@ -0,0 +1 @@ +optimize: true diff --git a/python/ql/test/query-tests/Statements/DocStrings/DocStrings.expected b/python/ql/test/query-tests/Statements/DocStrings/DocStrings.expected new file mode 100644 index 000000000000..29cebbf4f9d7 --- /dev/null +++ b/python/ql/test/query-tests/Statements/DocStrings/DocStrings.expected @@ -0,0 +1,4 @@ +| DocStrings.py:0:0:0:0 | Module DocStrings | Module DocStrings does not have a docstring | +| DocStrings.py:40:1:40:13 | Class Not_OK | Class Not_OK does not have a docstring | +| DocStrings.py:48:5:48:26 | Function meth_not_ok | Function meth_not_ok does not have a docstring | +| DocStrings.py:53:1:53:17 | Function not_ok | Function not_ok does not have a docstring | diff --git a/python/ql/test/query-tests/Statements/DocStrings/DocStrings.py b/python/ql/test/query-tests/Statements/DocStrings/DocStrings.py new file mode 100644 index 000000000000..9a5c0f01de92 --- /dev/null +++ b/python/ql/test/query-tests/Statements/DocStrings/DocStrings.py @@ -0,0 +1,59 @@ +#Modules should have docstrings + +class OK: + 'Classes need doc strings' + + 'Two strings' + pass + "Another string" +#All functions must be multi-line or there are ignored by the query + + +class _OK: + 'Unless they are private' + pass + pass + +def f_ok(x, y): + 'And functions' + pass + pass + +def _f_ok(y, z): + #Unless they are private + pass + pass + +class OK2: + 'doc-string' + + def meth_ok(self): + 'Methods need docstrings' + pass + pass + + def _meth_ok(self): + #Unless they are private + pass + pass + +class Not_OK: + #No docstring + + def meth_ok(self): + 'Methods need docstrings' + pass + pass + + def meth_not_ok(self): + #No doc-string + pass + pass + +def not_ok(x, y): + #Should have a doc-string + pass + pass + + + diff --git a/python/ql/test/query-tests/Statements/DocStrings/DocStrings.qlref b/python/ql/test/query-tests/Statements/DocStrings/DocStrings.qlref new file mode 100644 index 000000000000..1ff50d155fb2 --- /dev/null +++ b/python/ql/test/query-tests/Statements/DocStrings/DocStrings.qlref @@ -0,0 +1 @@ +Statements/DocStrings.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Statements/asserts/AssertLiteralConstant.expected b/python/ql/test/query-tests/Statements/asserts/AssertLiteralConstant.expected new file mode 100644 index 000000000000..b45a5f5d86d6 --- /dev/null +++ b/python/ql/test/query-tests/Statements/asserts/AssertLiteralConstant.expected @@ -0,0 +1,10 @@ +| assert.py:34:9:34:26 | Assert | Assert of literal constant True. | +| assert.py:36:9:36:19 | Assert | Assert of literal constant True. | +| assert.py:40:9:40:27 | Assert | Assert of literal constant False. | +| assert.py:44:9:44:23 | Assert | Assert of literal constant 0. | +| assert.py:48:9:48:23 | Assert | Assert of literal constant 1. | +| assert.py:52:9:52:24 | Assert | Assert of literal constant "". | +| assert.py:56:9:56:25 | Assert | Assert of literal constant "X". | +| assert.py:58:9:58:18 | Assert | Assert of literal constant "X". | +| assert.py:94:9:94:29 | Assert | Assert of literal constant False. | +| assert.py:102:9:102:29 | Assert | Assert of literal constant False. | diff --git a/python/ql/test/query-tests/Statements/asserts/AssertLiteralConstant.qlref b/python/ql/test/query-tests/Statements/asserts/AssertLiteralConstant.qlref new file mode 100644 index 000000000000..1f301867a08b --- /dev/null +++ b/python/ql/test/query-tests/Statements/asserts/AssertLiteralConstant.qlref @@ -0,0 +1 @@ +Statements/AssertLiteralConstant.ql diff --git a/python/ql/test/query-tests/Statements/asserts/AssertOnTuple.expected b/python/ql/test/query-tests/Statements/asserts/AssertOnTuple.expected new file mode 100644 index 000000000000..7945ccaf586d --- /dev/null +++ b/python/ql/test/query-tests/Statements/asserts/AssertOnTuple.expected @@ -0,0 +1,2 @@ +| assert.py:16:5:16:13 | Assert | Assertion of empty tuple is always False. | +| assert.py:17:5:17:17 | Assert | Assertion of non-empty tuple is always True. | diff --git a/python/ql/test/query-tests/Statements/asserts/AssertOnTuple.qlref b/python/ql/test/query-tests/Statements/asserts/AssertOnTuple.qlref new file mode 100644 index 000000000000..6fe2960c1c5b --- /dev/null +++ b/python/ql/test/query-tests/Statements/asserts/AssertOnTuple.qlref @@ -0,0 +1 @@ +Statements/AssertOnTuple.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected b/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected new file mode 100644 index 000000000000..4f46cff1e76f --- /dev/null +++ b/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected @@ -0,0 +1,2 @@ +| assert.py:5:5:5:20 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:5:13:5:19 | Yield | an expression | +| assert.py:8:5:8:22 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:8:12:8:22 | Attribute() | an expression | diff --git a/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.qlref b/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.qlref new file mode 100644 index 000000000000..7dd70f3fed29 --- /dev/null +++ b/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.qlref @@ -0,0 +1 @@ +Statements/SideEffectInAssert.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Statements/asserts/assert.py b/python/ql/test/query-tests/Statements/asserts/assert.py new file mode 100644 index 000000000000..854fffa3382b --- /dev/null +++ b/python/ql/test/query-tests/Statements/asserts/assert.py @@ -0,0 +1,106 @@ +import sys +import six + +def _f(): + assert (yield 3) + x = [ 1 ] + assert len(x) #Call without side-effects + assert sys.exit(1) #Call with side-effects + expected_types = (Response, six.text_type, six.binary_type) + assert isinstance(obj, expected_types), \ + "obj must be %s, not %s" % ( + " or ".join(t.__name__ for t in expected_types), + type(obj).__name__) + +def assert_tuple(x, y): + assert () + assert (x, y) + + + + + + + + + + + + + + +def error_assert_true(x): + if x: + assert True, "Bad" + else: + assert True + +def error_assert_false(x): + if x: + assert False, "Bad" + +def error_assert_zero(x): + if x: + assert 0, "Bad" + +def error_assert_one(x): + if x: + assert 1, "Bad" + +def error_assert_empty_string(x): + if x: + assert "", "Bad" + +def error_assert_nonempty_string(x): + if x: + assert "X", "Bad" + else: + assert "X" + +def ok_assert_false(x): + if x: + assert 0==1, "Ok" + +class TestCase: + pass + +class MyTest(TestCase): + def test_ok_assert_in_test(self, x): + if x: + assert False, "Ok" + +def ok_assert_in_final_branch3(x): + if foo(x): + pass + elif bar(x): + pass + elif quux(x): + pass + else: + assert False, "Ok" + +def ok_assert_in_final_branch2(x): + if foo(x): + pass + elif bar(x): + pass + else: + assert False, "Ok" + +def error_assert_in_final_branch1(x): + if foo(x): + pass + else: + assert False, "Error" + +def error_assert_in_intermediate_branch(x): + if foo(x): + pass + elif bar(x): + pass + elif quux(x): + assert False, "Error" + elif yks(x): + pass + else: + pass \ No newline at end of file diff --git a/python/ql/test/query-tests/Statements/exit/UseOfExit.expected b/python/ql/test/query-tests/Statements/exit/UseOfExit.expected new file mode 100644 index 000000000000..76984527df61 --- /dev/null +++ b/python/ql/test/query-tests/Statements/exit/UseOfExit.expected @@ -0,0 +1 @@ +| test.py:7:9:7:15 | ControlFlowNode for exit() | The 'exit' site.Quitter object may not exist if the 'site' module is not loaded or is modified. | diff --git a/python/ql/test/query-tests/Statements/exit/UseOfExit.qlref b/python/ql/test/query-tests/Statements/exit/UseOfExit.qlref new file mode 100644 index 000000000000..5925d9339140 --- /dev/null +++ b/python/ql/test/query-tests/Statements/exit/UseOfExit.qlref @@ -0,0 +1 @@ +Statements/UseOfExit.ql diff --git a/python/ql/test/query-tests/Statements/exit/test.py b/python/ql/test/query-tests/Statements/exit/test.py new file mode 100644 index 000000000000..ae03479497e7 --- /dev/null +++ b/python/ql/test/query-tests/Statements/exit/test.py @@ -0,0 +1,7 @@ + +def main(): + try: + process() + except Exception as ex: + print(ex) + exit(1) diff --git a/python/ql/test/query-tests/Statements/general/BreakOrReturnInFinally.expected b/python/ql/test/query-tests/Statements/general/BreakOrReturnInFinally.expected new file mode 100644 index 000000000000..57ec1428a5c8 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/BreakOrReturnInFinally.expected @@ -0,0 +1,3 @@ +| test.py:11:13:11:17 | Break | 'break' in a finally block will swallow any exceptions raised. | +| test.py:19:13:19:20 | Return | 'return' in a finally block will swallow any exceptions raised. | +| test.py:37:13:37:18 | Return | 'return' in a finally block will swallow any exceptions raised. | diff --git a/python/ql/test/query-tests/Statements/general/BreakOrReturnInFinally.qlref b/python/ql/test/query-tests/Statements/general/BreakOrReturnInFinally.qlref new file mode 100644 index 000000000000..0a710cdde9c0 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/BreakOrReturnInFinally.qlref @@ -0,0 +1 @@ +Statements/BreakOrReturnInFinally.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Statements/general/C_StyleParentheses.expected b/python/ql/test/query-tests/Statements/general/C_StyleParentheses.expected new file mode 100644 index 000000000000..a7fa79d219a3 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/C_StyleParentheses.expected @@ -0,0 +1,4 @@ +| test.py:109:5:109:8 | cond | Parenthesized condition in 'if' statement. | +| test.py:112:8:112:11 | cond | Parenthesized condition in 'while' statement. | +| test.py:115:9:115:12 | test | Parenthesized test in 'assert' statement. | +| test.py:118:13:118:13 | x | Parenthesized value in 'return' statement. | diff --git a/python/ql/test/query-tests/Statements/general/C_StyleParentheses.qlref b/python/ql/test/query-tests/Statements/general/C_StyleParentheses.qlref new file mode 100644 index 000000000000..6af1d43f7c65 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/C_StyleParentheses.qlref @@ -0,0 +1 @@ +Statements/C_StyleParentheses.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Statements/general/ConstantInConditional.expected b/python/ql/test/query-tests/Statements/general/ConstantInConditional.expected new file mode 100644 index 000000000000..95d67655558c --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/ConstantInConditional.expected @@ -0,0 +1,2 @@ +| statements_test.py:5:8:5:11 | True | Testing a constant will always give the same result. | +| statements_test.py:9:8:9:8 | IntegerLiteral | Testing a constant will always give the same result. | diff --git a/python/ql/test/query-tests/Statements/general/ConstantInConditional.qlref b/python/ql/test/query-tests/Statements/general/ConstantInConditional.qlref new file mode 100644 index 000000000000..c4b9b9a555be --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/ConstantInConditional.qlref @@ -0,0 +1 @@ +Statements/ConstantInConditional.ql diff --git a/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected b/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected new file mode 100644 index 000000000000..88b3dfbb008a --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected @@ -0,0 +1,4 @@ +| statements_test.py:19:5:19:18 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 2. | statements_test.py:19:15:19:18 | statements_test.py:19 | tuple | +| statements_test.py:163:5:163:23 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:163:13:163:23 | statements_test.py:163 | list | +| statements_test.py:172:5:172:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:167:16:167:24 | statements_test.py:167 | tuple | +| statements_test.py:172:5:172:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 6. | statements_test.py:169:16:169:26 | statements_test.py:169 | tuple | diff --git a/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.qlref b/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.qlref new file mode 100644 index 000000000000..f7e01617f9e1 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.qlref @@ -0,0 +1 @@ +Statements/MismatchInMultipleAssignment.ql diff --git a/python/ql/test/query-tests/Statements/general/ModificationOfLocals.expected b/python/ql/test/query-tests/Statements/general/ModificationOfLocals.expected new file mode 100644 index 000000000000..547cec0c3b81 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/ModificationOfLocals.expected @@ -0,0 +1,5 @@ +| test.py:98:5:98:17 | Subscript | Modification of the locals() dictionary will have no effect on the local variables. | +| test.py:100:5:100:28 | Attribute() | Modification of the locals() dictionary will have no effect on the local variables. | +| test.py:101:5:101:14 | Attribute() | Modification of the locals() dictionary will have no effect on the local variables. | +| test.py:102:9:102:14 | Subscript | Modification of the locals() dictionary will have no effect on the local variables. | +| test.py:103:5:103:13 | Attribute() | Modification of the locals() dictionary will have no effect on the local variables. | diff --git a/python/ql/test/query-tests/Statements/general/ModificationOfLocals.qlref b/python/ql/test/query-tests/Statements/general/ModificationOfLocals.qlref new file mode 100644 index 000000000000..88a666435e31 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/ModificationOfLocals.qlref @@ -0,0 +1 @@ +Statements/ModificationOfLocals.ql diff --git a/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariable.expected b/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariable.expected new file mode 100644 index 000000000000..b7b3f0e56ed9 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariable.expected @@ -0,0 +1,6 @@ +| nested.py:3:9:3:38 | For | Nested for statement uses loop variable 'repeated_var' of enclosing $@. | nested.py:2:5:2:34 | For | for statement | +| nested.py:19:13:19:23 | For | Nested for statement uses loop variable 'x' of enclosing $@. | nested.py:17:5:17:15 | For | for statement | +| nested.py:27:9:27:19 | For | Nested for statement uses loop variable 'x' of enclosing $@. | nested.py:25:5:25:15 | For | for statement | +| nested.py:35:9:35:19 | For | Nested for statement uses loop variable 'x' of enclosing $@. | nested.py:32:5:32:15 | For | for statement | +| nested.py:42:9:42:19 | For | Nested for statement uses loop variable 'x' of enclosing $@. | nested.py:41:5:41:15 | For | for statement | +| nested.py:50:9:50:19 | For | Nested for statement uses loop variable 'x' of enclosing $@. | nested.py:48:5:48:15 | For | for statement | diff --git a/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariable.qlref b/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariable.qlref new file mode 100644 index 000000000000..75a4032a05ff --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariable.qlref @@ -0,0 +1 @@ +Statements/NestedLoopsSameVariable.ql diff --git a/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariableWithReuse.expected b/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariableWithReuse.expected new file mode 100644 index 000000000000..086f12ea27c3 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariableWithReuse.expected @@ -0,0 +1,3 @@ +| nested.py:3:9:3:38 | For | Nested for statement $@ loop variable 'repeated_var' of enclosing $@. | nested.py:5:22:5:33 | repeated_var | uses | nested.py:2:5:2:34 | For | for statement | +| nested.py:27:9:27:19 | For | Nested for statement $@ loop variable 'x' of enclosing $@. | nested.py:29:13:29:13 | x | uses | nested.py:25:5:25:15 | For | for statement | +| nested.py:50:9:50:19 | For | Nested for statement $@ loop variable 'x' of enclosing $@. | nested.py:54:13:54:13 | x | uses | nested.py:48:5:48:15 | For | for statement | diff --git a/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariableWithReuse.qlref b/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariableWithReuse.qlref new file mode 100644 index 000000000000..d34f2ddb21a6 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/NestedLoopsSameVariableWithReuse.qlref @@ -0,0 +1 @@ +Statements/NestedLoopsSameVariableWithReuse.ql diff --git a/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.expected b/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.expected new file mode 100755 index 000000000000..308f0aa027e4 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.expected @@ -0,0 +1 @@ +| test.py:50:1:50:23 | For | $@ of class '$@' may be used in for-loop. | test.py:50:10:50:22 | ControlFlowNode for NonIterator() | Non-iterator | test.py:45:1:45:26 | class NonIterator | NonIterator | diff --git a/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.qlref b/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.qlref new file mode 100644 index 000000000000..fb09cace29a8 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.qlref @@ -0,0 +1 @@ +Statements/NonIteratorInForLoop.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Statements/general/RedundantAssignment.expected b/python/ql/test/query-tests/Statements/general/RedundantAssignment.expected new file mode 100644 index 000000000000..f997ac54932d --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/RedundantAssignment.expected @@ -0,0 +1,3 @@ +| statements_test.py:54:5:54:9 | AssignStmt | This assignment assigns a variable to itself. | +| statements_test.py:57:9:57:19 | AssignStmt | This assignment assigns a variable to itself. | +| statements_test.py:117:9:117:23 | AssignStmt | This assignment assigns a variable to itself. | diff --git a/python/ql/test/query-tests/Statements/general/RedundantAssignment.qlref b/python/ql/test/query-tests/Statements/general/RedundantAssignment.qlref new file mode 100644 index 000000000000..2c3b08d87660 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/RedundantAssignment.qlref @@ -0,0 +1 @@ +Statements/RedundantAssignment.ql diff --git a/python/ql/test/query-tests/Statements/general/ShouldUseWithStatement.expected b/python/ql/test/query-tests/Statements/general/ShouldUseWithStatement.expected new file mode 100644 index 000000000000..41a8723db03b --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/ShouldUseWithStatement.expected @@ -0,0 +1 @@ +| test.py:162:9:162:17 | Attribute() | Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement. | test.py:145:1:145:17 | class CM | CM | diff --git a/python/ql/test/query-tests/Statements/general/ShouldUseWithStatement.qlref b/python/ql/test/query-tests/Statements/general/ShouldUseWithStatement.qlref new file mode 100644 index 000000000000..e1b3d7276ff8 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/ShouldUseWithStatement.qlref @@ -0,0 +1 @@ +Statements/ShouldUseWithStatement.ql diff --git a/python/ql/test/query-tests/Statements/general/StringConcatenationInLoop.expected b/python/ql/test/query-tests/Statements/general/StringConcatenationInLoop.expected new file mode 100644 index 000000000000..418308f116d8 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/StringConcatenationInLoop.expected @@ -0,0 +1,2 @@ +| performance.py:6:9:6:20 | AugAssign | String concatenation in a loop is quadratic in the number of iterations. | +| performance.py:12:9:12:29 | AssignStmt | String concatenation in a loop is quadratic in the number of iterations. | diff --git a/python/ql/test/query-tests/Statements/general/StringConcatenationInLoop.qlref b/python/ql/test/query-tests/Statements/general/StringConcatenationInLoop.qlref new file mode 100644 index 000000000000..cf694f99e48a --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/StringConcatenationInLoop.qlref @@ -0,0 +1 @@ +Statements/StringConcatenationInLoop.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected b/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected new file mode 100644 index 000000000000..f6f7ca1bad27 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected @@ -0,0 +1 @@ +| statements_test.py:181:5:181:9 | Delete | Unnecessary deletion of local variable $@ in function $@. | statements_test.py:181:9:181:9 | statements_test.py:181 | x | statements_test.py:179:1:179:31 | statements_test.py:179 | error_unnecessary_delete | diff --git a/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.qlref b/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.qlref new file mode 100644 index 000000000000..e97bf9a872ba --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.qlref @@ -0,0 +1 @@ +Statements/UnnecessaryDelete.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.expected b/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.expected new file mode 100644 index 000000000000..6f333fc7bbf1 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.expected @@ -0,0 +1,2 @@ +| statements_test.py:63:1:63:19 | For | This 'for' statement has a redundant 'else' as no 'break' is present in the body. | +| statements_test.py:68:1:68:13 | While | This 'while' statement has a redundant 'else' as no 'break' is present in the body. | diff --git a/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.qlref b/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.qlref new file mode 100644 index 000000000000..8f92883475c6 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.qlref @@ -0,0 +1 @@ +Statements/UnnecessaryElseClause.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Statements/general/UnnecessaryPass.expected b/python/ql/test/query-tests/Statements/general/UnnecessaryPass.expected new file mode 100644 index 000000000000..43e81cd1e163 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/UnnecessaryPass.expected @@ -0,0 +1 @@ +| test.py:41:5:41:8 | Pass | Unnecessary 'pass' statement. | diff --git a/python/ql/test/query-tests/Statements/general/UnnecessaryPass.qlref b/python/ql/test/query-tests/Statements/general/UnnecessaryPass.qlref new file mode 100644 index 000000000000..259ad4df6383 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/UnnecessaryPass.qlref @@ -0,0 +1 @@ +Statements/UnnecessaryPass.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Statements/general/nested.py b/python/ql/test/query-tests/Statements/general/nested.py new file mode 100644 index 000000000000..ac06f2974aa6 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/nested.py @@ -0,0 +1,54 @@ +def f1(): + for repeated_var in range(10): + for repeated_var in range(10): + pass + do_something(repeated_var) + +def f2(): + for repeated_but_ok in range(10): + if repeated_but_ok == 7: + break + else: + for repeated_but_ok in range(10): + pass + do_something(repeated_but_ok) + +def f3(y,p): + for x in y: + if p(y): + for x in y: + good1(x) + else: + good2(x) + +def f4(y): + for x in y: + good1(x) + for x in y: + good2(x) + bad(x) + +def f5(y): + for x in y: + good1(x) + temp = x + for x in y: + good2(x) + x = temp + good3(x) + +def f6(y, f): + for x in y: + for x in y: + good(x) + x = f(x) + bad(x) + +def f7(y,p): + for x in y: + good(x) + for x in y: + if p(x): + x = 1 + break + bad(x) diff --git a/python/ql/test/query-tests/Statements/general/performance.py b/python/ql/test/query-tests/Statements/general/performance.py new file mode 100644 index 000000000000..261f8bad1ef6 --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/performance.py @@ -0,0 +1,12 @@ + +#String concat in loop +def y(seq): + y_accum = '' + for s in seq: + y_accum += s + + +def z(seq): + z_accum = '' + for s in seq: + z_accum = z_accum + s diff --git a/python/ql/test/query-tests/Statements/general/statements_test.py b/python/ql/test/query-tests/Statements/general/statements_test.py new file mode 100644 index 000000000000..37ed94ec57ee --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/statements_test.py @@ -0,0 +1,200 @@ + +#Constant in conditional + +def cc1(): + if True: + print("Hi") + +def cc2(): + if 3: + print("Hi") + +def not_cc(): + #Don't treat __debug__ as a constant. It may be immutable, but it is not constant from run to run. + if __debug__: + print("Hi") + +#Mismatch in multiple assignment +def mima(): + x, y, z = 1, 2 + return x, y, z + +#Statement has no effect (4 statements, 3 of which are violations) +"Not a docstring" # This is acceptable as strings can be used as comments. +len +sys.argv + [] +3 == 4 + +#The 'sys' statements have an effect +try: + sys +except: + do_something() + +def exec_used(val): + exec (val) + +#This has an effect: +def effectful(y): + [x for x in y] + + + + + +#Redundant assignment + +ok = 1 +# some people use self-assignment to shut Pyflakes up +ok = ok # Pyflakes + +class Redundant(object): + + x = x # OK + x = x # violation + + def __init__(self, args): + args = args # violation + +#Non redundant assignment +len = len + +#Pointless else clauses +for x in range(10): + func(x) +else: + do_something() + +while x < 10: + func(x) +else: + do_something() + +#OK else clauses: +for x in range(10): + if func(x): + break +else: + do_something() + +while x < 10: + if func(x): + break +else: + do_something() + + + + + + + + + + + +#Not a redundant assignment if a property. +class WithProp(object): + + @property + def x(self): + return self._x + + @prop.setter + def set_x(self, x): + side_effect(x) + self._x = x + + def meth(self): + self.x = self.x + +def maybe_property(x): + x.y = x.y + +class WithoutProp(object): + + def meth(self): + self.x = self.x + +#Accessing a property has an effect: +def prop_acc(): + p = WithProp() + p.x + +#Fake Enum class + +class EnumMeta(type): + def __iter__(self): + return unknown() + +import six + +@six.add_metaclass(EnumMeta) +class Enum: + pass + +#ODASA-3777 +class EnumDerived(Enum): + a = 0 + b = 1 + +for e in EnumDerived: + print(e) + +class SideEffectingAttr(object): + + def __setattr__(self, name, val): + print("hello!") + +s = SideEffectingAttr() +s.foo = s.foo + +#Unreachable, so don't flag as constant test. +if False: + a = 1 + + + + + + + +def error_mismatched_multi_assign_list(): + a,b,c = [1,2,3,4,5] + +def returning_different_tuple_sizes(x): + if x: + return 1,2,3,4,5 + else: + return 1,2,3,4,5,6 + +def error_indirect_mismatched_multi_assign(x): + a, b, c = returning_different_tuple_sizes(x) + return a, b, c + + + + +#ODASA-6754 +def error_unnecessary_delete(): + x = big_object() + del x + +def ok_delete_in_loop(): + y = 0 + for i in range(10): + x = big_object(i) + y += calculation(x) + del x + +def ok_delete_yield(): + x = big_object() + y = calculation(x) + del x + yield y + +def ok_delete_exc_info_cycle_breaker(): + import sys + t, v, tb = sys.exc_info() + y = calculation(t,v,tb) + del t, v, tb diff --git a/python/ql/test/query-tests/Statements/general/test.py b/python/ql/test/query-tests/Statements/general/test.py new file mode 100644 index 000000000000..ab3c53cfcaed --- /dev/null +++ b/python/ql/test/query-tests/Statements/general/test.py @@ -0,0 +1,168 @@ + + + + + +def break_in_finally(seq, x): + for i in seq: + try: + x() + finally: + break + return 0 + +def return_in_finally(seq, x): + for i in seq: + try: + x() + finally: + return 1 + return 0 + +#Break in loop in finally +#This is OK +def return_in_loop_in_finally(f, seq): + try: + f() + finally: + for i in seq: + break + +#But this is not +def return_in_loop_in_finally(f, seq): + try: + f() + finally: + for i in seq: + return + +def unnecessary_pass(arg): + print (arg) + pass + +#Non-iterator in for loop + +class NonIterator(object): + + def __init__(self): + pass + +for x in NonIterator(): + do_something(x) + +#None in for loop + +def dodgy_iter(x): + if x: + s = None + else: + s = [0,1] + #Error -- Could be None + for i in s: + print(i) + #Ok Test for None + if s: + for i in s: + print(i) + if s is not None: + for i in s: + print(i) + +#OK to iterate over: +class GetItem(object): + + def __getitem__(self, i): + return i + +for y in GetItem(): + pass + +class D(dict): pass + +for z in D(): + pass + + + + + + + + + + + + +def modification_of_locals(): + x = 0 + locals()['x'] = 1 + l = locals() + l.update({'x':1, 'y':2}) + l.pop('y') + del l['x'] + l.clear() + return x + + +#C-style things + +if (cond): + pass + +while (cond): + pass + +assert (test) + +def parens(x): + return (x) + + +#ODASA-2038 +class classproperty(object): + + def __init__(self, getter): + self.getter = getter + + def __get__(self, instance, instance_type): + return self.getter(instance_type) + +class WithClassProperty(object): + + @classproperty + def x(self): + return [0] + +wcp = WithClassProperty() + +for i in wcp.x: + assert x == 0 +for i in WithClassProperty.x: + assert x == 0 + +#Should use context mamager + +class CM(object): + + def __enter__(self): + pass + + def __exit__(self, ex, cls, tb): + pass + + def write(self, data): + pass + +def no_with(): + f = CM() + try: + f.write("Hello ") + f.write(" World\n") + finally: + f.close() + +#Assert without side-effect +def assert_ok(seq): + assert all(isinstance(element, (str, unicode)) for element in seq) + + diff --git a/python/ql/test/query-tests/Statements/no_effect/StatementNoEffect.expected b/python/ql/test/query-tests/Statements/no_effect/StatementNoEffect.expected new file mode 100644 index 000000000000..5b1626128a44 --- /dev/null +++ b/python/ql/test/query-tests/Statements/no_effect/StatementNoEffect.expected @@ -0,0 +1,5 @@ +| test.py:24:1:24:3 | ExprStmt | This statement has no effect. | +| test.py:25:1:25:13 | ExprStmt | This statement has no effect. | +| test.py:26:1:26:6 | ExprStmt | This statement has no effect. | +| test.py:80:1:80:6 | ExprStmt | This statement has no effect. | +| test.py:116:5:116:9 | ExprStmt | This statement has no effect. | diff --git a/python/ql/test/query-tests/Statements/no_effect/StatementNoEffect.qlref b/python/ql/test/query-tests/Statements/no_effect/StatementNoEffect.qlref new file mode 100644 index 000000000000..300e28e96c61 --- /dev/null +++ b/python/ql/test/query-tests/Statements/no_effect/StatementNoEffect.qlref @@ -0,0 +1 @@ +Statements/StatementNoEffect.ql diff --git a/python/ql/test/query-tests/Statements/no_effect/UnusedExceptionObject.expected b/python/ql/test/query-tests/Statements/no_effect/UnusedExceptionObject.expected new file mode 100644 index 000000000000..217832385add --- /dev/null +++ b/python/ql/test/query-tests/Statements/no_effect/UnusedExceptionObject.expected @@ -0,0 +1 @@ +| test.py:127:9:127:26 | ValueError() | Instantiating an exception, but not raising it, has no effect | diff --git a/python/ql/test/query-tests/Statements/no_effect/UnusedExceptionObject.qlref b/python/ql/test/query-tests/Statements/no_effect/UnusedExceptionObject.qlref new file mode 100644 index 000000000000..ae7783be6af2 --- /dev/null +++ b/python/ql/test/query-tests/Statements/no_effect/UnusedExceptionObject.qlref @@ -0,0 +1 @@ +Statements/UnusedExceptionObject.ql diff --git a/python/ql/test/query-tests/Statements/no_effect/assert_raises.py b/python/ql/test/query-tests/Statements/no_effect/assert_raises.py new file mode 100644 index 000000000000..d9bd68252521 --- /dev/null +++ b/python/ql/test/query-tests/Statements/no_effect/assert_raises.py @@ -0,0 +1,11 @@ +import unittest + +class T(unittest.TestCase): + + def test_thing(self): + l = 10 + s = [0] + with self.assertRaises(TypeError): + l[1000] + with self.assertRaises(IndexError): + s[1] diff --git a/python/ql/test/query-tests/Statements/no_effect/notebook.py b/python/ql/test/query-tests/Statements/no_effect/notebook.py new file mode 100644 index 000000000000..9e31b462e7ec --- /dev/null +++ b/python/ql/test/query-tests/Statements/no_effect/notebook.py @@ -0,0 +1,26 @@ + +# This test is for IPython/Jupyter notebooks which can legitimately include bare expressions at the end of each code-cell. + +# 3.0 + +# +y = 1 + 1 +y + +# +z = 2 * 2 +z + +# +y + + + + + + + + + + + diff --git a/python/ql/test/query-tests/Statements/no_effect/options b/python/ql/test/query-tests/Statements/no_effect/options new file mode 100644 index 000000000000..b91afde07678 --- /dev/null +++ b/python/ql/test/query-tests/Statements/no_effect/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=2 diff --git a/python/ql/test/query-tests/Statements/no_effect/test.py b/python/ql/test/query-tests/Statements/no_effect/test.py new file mode 100644 index 000000000000..b31650a3b092 --- /dev/null +++ b/python/ql/test/query-tests/Statements/no_effect/test.py @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + +import sys + + +#Statement has no effect (4 statements, 3 of which are violations) +"Not a docstring" # This is acceptable as strings can be used as comments. +len +sys.argv + [] +3 == 4 + +#The 'sys' statements have an effect +try: + sys +except: + do_something() + +def exec_used(val): + exec (val) + +#This has an effect: +def effectful(y): + [x for x in y] + + +#Not a redundant assignment if a property. +class WithProp(object): + + @property + def x(self): + return self._x + + @prop.setter + def set_x(self, x): + side_effect(x) + self._x = x + + def meth(self): + self.x = self.x + +#Accessing a property has an effect: +def prop_acc(): + p = WithProp() + p.x + + +def mydecorator(func): + return property(fget=func) + +class X(object): + + @mydecorator + def deco(self): + pass + + def func(self): + pass + +x = X() +x.deco +x.deco + 2 + +#No effect +x.func + +#Cannot infer what attribute is, so be conservative +x.thing +other_thing.func + +#Name Error + +try: + unicode +except NameError: + #Python 3 + unicode = str + +try: + ascii +except NameError: + #Python 2 + def ascii(obj): + pass + + +#Overridden operator. yuck. +class Horrible(object): + + def __add__(self, other): + fire_missiles_at(other) + + def __lt__(self, other): + fire_guns_at(other) + + +def possible_fps(x): + h = Horrible() + h + "innocent bystander" + h < "upstanding citizen" + x - 3 #True positive + + +# Forgotten raise. + +def do_action_forgotten_raise(action): + if action == "go": + start() + elif action == "stop": + stop() + else: + ValueError(action) + +def do_action(action): + if action == "go": + start() + elif action == "stop": + stop() + else: + raise ValueError(action) diff --git a/python/ql/test/query-tests/Statements/unreachable/UnreachableCode.expected b/python/ql/test/query-tests/Statements/unreachable/UnreachableCode.expected new file mode 100644 index 000000000000..74e56473a619 --- /dev/null +++ b/python/ql/test/query-tests/Statements/unreachable/UnreachableCode.expected @@ -0,0 +1,6 @@ +| test.py:8:9:8:28 | AssignStmt | Unreachable statement. | +| test.py:10:9:10:28 | Return | Unreachable statement. | +| test.py:16:9:16:21 | ExprStmt | Unreachable statement. | +| test.py:21:5:21:38 | For | Unreachable statement. | +| test.py:28:9:28:21 | ExprStmt | Unreachable statement. | +| test.py:84:5:84:21 | ExceptStmt | Unreachable statement. | diff --git a/python/ql/test/query-tests/Statements/unreachable/UnreachableCode.qlref b/python/ql/test/query-tests/Statements/unreachable/UnreachableCode.qlref new file mode 100644 index 000000000000..5b7891f0026a --- /dev/null +++ b/python/ql/test/query-tests/Statements/unreachable/UnreachableCode.qlref @@ -0,0 +1 @@ +Statements/UnreachableCode.ql diff --git a/python/ql/test/query-tests/Statements/unreachable/test.py b/python/ql/test/query-tests/Statements/unreachable/test.py new file mode 100755 index 000000000000..b1b69942de9e --- /dev/null +++ b/python/ql/test/query-tests/Statements/unreachable/test.py @@ -0,0 +1,122 @@ + +#Unreachable code + +def f(x): + while x: + print (x) + while 0: + asgn = unreachable() + while False: + return unreachable() + while 7: + print(x) + +def g(x): + if False: + unreachable() + else: + reachable() + print(x) + return 5 + for x in first_unreachable_stmt(): + raise more_unreachable() + +def h(a,b): + if True: + reachable() + else: + unreachable() + +def intish(n): + """"Regression test - the 'except' statement is reachable""" + test = 0 + try: + test += n + except: + return False + return True + +#ODASA-2033 +def unexpected_return_result(): + try: + assert 0, "async.Return with argument inside async.generator function" + except AssertionError: + return (None, sys.exc_info()) + +#Yield may raise -- E.g. in a context manager +def handle_yield_exception(): + resources = get_resources() + try: + yield resources + except Exception as exc: + log(exc) + +#ODASA-ODASA-3790 +def isnt_iter(seq): + got_exc = False + try: + for x in seq: + pass + except Exception: + got_exc = True + return got_exc + + +class Odasa3686(object): + + def is_iterable(self, obj): + #special case string + if not object: + return False + if isinstance(obj, str): + return False + + #Test for iterability + try: + None in obj + return True + except TypeError: + return False + +def odasa5387(): + try: + str + except NameError: # Unreachable 'str' is always defined + pass + try: + unicode + except NameError: # Reachable as 'unicode' is undefined in Python 3 + pass + +#This is OK as type-hints require it +if False: + from typing import Any + +def foo(): + # type: () -> None + return + +#ODASA-6483 +def deliberate_name_error(cond): + if cond: + x = 0 + try: + x + except NameError: + x = 1 + return x + +#ODASA-6783 +def emtpy_gen(): + if False: + yield None + + +def foo(x): + if True: + if x < 3: + print(x, "< 3") + if x == 0: + print(x, "== 0") + + diff --git a/python/ql/test/query-tests/Testing/ImpreciseAssert.expected b/python/ql/test/query-tests/Testing/ImpreciseAssert.expected new file mode 100644 index 000000000000..e9e34a167699 --- /dev/null +++ b/python/ql/test/query-tests/Testing/ImpreciseAssert.expected @@ -0,0 +1,4 @@ +| test.py:6:9:6:31 | Attribute() | assertTrue(a == b) cannot provide an informative message. Using assertEqual(a, b) instead will give more informative messages. | +| test.py:7:9:7:31 | Attribute() | assertFalse(a > b) cannot provide an informative message. Using assertLessEqual(a, b) instead will give more informative messages. | +| test.py:8:9:8:33 | Attribute() | assertTrue(a in b) cannot provide an informative message. Using assertIn(a, b) instead will give more informative messages. | +| test.py:9:9:9:33 | Attribute() | assertFalse(a is b) cannot provide an informative message. Using assertIsNot(a, b) instead will give more informative messages. | diff --git a/python/ql/test/query-tests/Testing/ImpreciseAssert.qlref b/python/ql/test/query-tests/Testing/ImpreciseAssert.qlref new file mode 100644 index 000000000000..79dc019fe794 --- /dev/null +++ b/python/ql/test/query-tests/Testing/ImpreciseAssert.qlref @@ -0,0 +1 @@ +Testing/ImpreciseAssert.ql diff --git a/python/ql/test/query-tests/Testing/test.py b/python/ql/test/query-tests/Testing/test.py new file mode 100644 index 000000000000..1c79f177ac60 --- /dev/null +++ b/python/ql/test/query-tests/Testing/test.py @@ -0,0 +1,9 @@ +from unittest import TestCase + +class MyTest(TestCase): + + def test1(self): + self.assertTrue(1 == 1) + self.assertFalse(1 > 2) + self.assertTrue(1 in [1]) + self.assertFalse(0 is "") diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateBlock.expected b/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateBlock.expected new file mode 100644 index 000000000000..9234fea78c7b --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateBlock.expected @@ -0,0 +1,8 @@ +| duplicate_test.py:47:9:60:17 | Duplicate code: 14 duplicated lines. | Duplicate code: 14 lines are duplicated at duplicate_test.py:9 | +| duplicate_test.py:56:18:66:25 | Duplicate code: 11 duplicated lines. | Duplicate code: 11 lines are duplicated at duplicate_test.py:18 | +| duplicate_test.py:61:24:80:32 | Duplicate code: 20 duplicated lines. | Duplicate code: 20 lines are duplicated at duplicate_test.py:23 | +| duplicate_test.py:166:18:245:24 | Duplicate code: 80 duplicated lines. | Duplicate code: 80 lines are duplicated at duplicate_test.py:84 | +| duplicate_test.py:287:9:300:17 | Duplicate code: 14 duplicated lines. | Duplicate code: 14 lines are duplicated at duplicate_test.py:9 | +| duplicate_test.py:287:9:300:17 | Duplicate code: 14 duplicated lines. | Duplicate code: 14 lines are duplicated at duplicate_test.py:47 | +| duplicate_test.py:299:22:318:32 | Duplicate code: 20 duplicated lines. | Duplicate code: 20 lines are duplicated at duplicate_test.py:23 | +| duplicate_test.py:299:22:318:32 | Duplicate code: 20 duplicated lines. | Duplicate code: 20 lines are duplicated at duplicate_test.py:61 | diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateBlock.qlref b/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateBlock.qlref new file mode 100644 index 000000000000..6e6d439a0409 --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateBlock.qlref @@ -0,0 +1 @@ +external/DuplicateBlock.ql diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateFunction.expected b/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateFunction.expected new file mode 100644 index 000000000000..c85079e3811c --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateFunction.expected @@ -0,0 +1,4 @@ +| duplicate_test.py:9:1:9:16 | Function dis | All 26 statements in dis are identical in $@. | duplicate_test.py:47:1:47:17 | Function dis2 | dis2 | +| duplicate_test.py:47:1:47:17 | Function dis2 | All 26 statements in dis2 are identical in $@. | duplicate_test.py:9:1:9:16 | Function dis | dis | +| duplicate_test.py:287:1:287:17 | Function dis4 | All 24 statements in dis4 are identical in $@. | duplicate_test.py:9:1:9:16 | Function dis | dis | +| duplicate_test.py:287:1:287:17 | Function dis4 | All 24 statements in dis4 are identical in $@. | duplicate_test.py:47:1:47:17 | Function dis2 | dis2 | diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateFunction.qlref b/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateFunction.qlref new file mode 100644 index 000000000000..bb7e3a454178 --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/DuplicateFunction.qlref @@ -0,0 +1 @@ +external/DuplicateFunction.ql diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlyDuplicateClass.expected b/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlyDuplicateClass.expected new file mode 100644 index 000000000000..e628b3719447 --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlyDuplicateClass.expected @@ -0,0 +1,2 @@ +| duplicate_test.py:84:1:84:13 | Class Popen3 | All 55 statements in Popen3 are identical in $@. | duplicate_test.py:166:1:166:18 | Class Popen3Again | Popen3Again | +| duplicate_test.py:166:1:166:18 | Class Popen3Again | All 55 statements in Popen3Again are identical in $@. | duplicate_test.py:84:1:84:13 | Class Popen3 | Popen3 | diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlyDuplicateClass.qlref b/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlyDuplicateClass.qlref new file mode 100644 index 000000000000..4af1d7b760b5 --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlyDuplicateClass.qlref @@ -0,0 +1 @@ +external/MostlyDuplicateClass.ql diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlyDuplicateFile.expected b/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlyDuplicateFile.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlyDuplicateFile.qlref b/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlyDuplicateFile.qlref new file mode 100644 index 000000000000..ae06f160dec8 --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlyDuplicateFile.qlref @@ -0,0 +1 @@ +external/MostlyDuplicateFile.ql diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlySimilarFile.expected b/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlySimilarFile.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlySimilarFile.qlref b/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlySimilarFile.qlref new file mode 100644 index 000000000000..f8d493b0a554 --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/MostlySimilarFile.qlref @@ -0,0 +1 @@ +external/MostlySimilarFile.ql diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/SimilarFunction.expected b/python/ql/test/query-tests/UselessCode/DuplicateCode/SimilarFunction.expected new file mode 100644 index 000000000000..55408a913453 --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/SimilarFunction.expected @@ -0,0 +1,12 @@ +| duplicate_test.py:9:1:9:16 | Function dis | All statements in dis are similar in $@. | duplicate_test.py:249:1:249:17 | Function dis3 | dis3 | +| duplicate_test.py:9:1:9:16 | Function dis | All statements in dis are similar in $@. | duplicate_test.py:323:1:323:17 | Function dis5 | dis5 | +| duplicate_test.py:47:1:47:17 | Function dis2 | All statements in dis2 are similar in $@. | duplicate_test.py:249:1:249:17 | Function dis3 | dis3 | +| duplicate_test.py:47:1:47:17 | Function dis2 | All statements in dis2 are similar in $@. | duplicate_test.py:323:1:323:17 | Function dis5 | dis5 | +| duplicate_test.py:249:1:249:17 | Function dis3 | All statements in dis3 are similar in $@. | duplicate_test.py:9:1:9:16 | Function dis | dis | +| duplicate_test.py:249:1:249:17 | Function dis3 | All statements in dis3 are similar in $@. | duplicate_test.py:47:1:47:17 | Function dis2 | dis2 | +| duplicate_test.py:249:1:249:17 | Function dis3 | All statements in dis3 are similar in $@. | duplicate_test.py:323:1:323:17 | Function dis5 | dis5 | +| duplicate_test.py:287:1:287:17 | Function dis4 | All statements in dis4 are similar in $@. | duplicate_test.py:249:1:249:17 | Function dis3 | dis3 | +| duplicate_test.py:287:1:287:17 | Function dis4 | All statements in dis4 are similar in $@. | duplicate_test.py:323:1:323:17 | Function dis5 | dis5 | +| duplicate_test.py:323:1:323:17 | Function dis5 | All statements in dis5 are similar in $@. | duplicate_test.py:9:1:9:16 | Function dis | dis | +| duplicate_test.py:323:1:323:17 | Function dis5 | All statements in dis5 are similar in $@. | duplicate_test.py:47:1:47:17 | Function dis2 | dis2 | +| duplicate_test.py:323:1:323:17 | Function dis5 | All statements in dis5 are similar in $@. | duplicate_test.py:249:1:249:17 | Function dis3 | dis3 | diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/SimilarFunction.qlref b/python/ql/test/query-tests/UselessCode/DuplicateCode/SimilarFunction.qlref new file mode 100644 index 000000000000..665253817d77 --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/SimilarFunction.qlref @@ -0,0 +1 @@ +external/SimilarFunction.ql diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/duplicate_test.py b/python/ql/test/query-tests/UselessCode/DuplicateCode/duplicate_test.py new file mode 100644 index 000000000000..cebfd7aca8a6 --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/duplicate_test.py @@ -0,0 +1,358 @@ +#Code Duplication + + +#Exact duplication of function + +#Code copied from stdlib, copyright PSF. +#See http://www.python.org/download/releases/2.7/license/ + +def dis(x=None): + """Disassemble classes, methods, functions, or code. + + With no argument, disassemble the last traceback. + + """ + if x is None: + distb() + return + if isinstance(x, types.InstanceType): + x = x.__class__ + if hasattr(x, 'im_func'): + x = x.im_func + if hasattr(x, 'func_code'): + x = x.func_code + if hasattr(x, '__dict__'): + items = x.__dict__.items() + items.sort() + for name, x1 in items: + if isinstance(x1, _have_code): + print("Disassembly of %s:" % name) + try: + dis(x1) + except TypeError(msg): + print("Sorry:", msg) + print() + elif hasattr(x, 'co_code'): + disassemble(x) + elif isinstance(x, str): + disassemble_string(x) + else: + raise TypeError( + "don't know how to disassemble %s objects" % + type(x).__name__) + + +#And duplicate version + +def dis2(x=None): + """Disassemble classes, methods, functions, or code. + + With no argument, disassemble the last traceback. + + """ + if x is None: + distb() + return + if isinstance(x, types.InstanceType): + x = x.__class__ + if hasattr(x, 'im_func'): + x = x.im_func + if hasattr(x, 'func_code'): + x = x.func_code + if hasattr(x, '__dict__'): + items = x.__dict__.items() + items.sort() + for name, x1 in items: + if isinstance(x1, _have_code): + print("Disassembly of %s:" % name) + try: + dis(x1) + except TypeError(msg): + print("Sorry:", msg) + print() + elif hasattr(x, 'co_code'): + disassemble(x) + elif isinstance(x, str): + disassemble_string(x) + else: + raise TypeError( + "don't know how to disassemble %s objects" % + type(x).__name__) + +#Exactly duplicate class + +class Popen3: + """Class representing a child process. Normally, instances are created + internally by the functions popen2() and popen3().""" + + sts = -1 # Child not completed yet + + def __init__(self, cmd, capturestderr=False, bufsize=-1): + """The parameter 'cmd' is the shell command to execute in a + sub-process. On UNIX, 'cmd' may be a sequence, in which case arguments + will be passed directly to the program without shell intervention (as + with os.spawnv()). If 'cmd' is a string it will be passed to the shell + (as with os.system()). The 'capturestderr' flag, if true, specifies + that the object should capture standard error output of the child + process. The default is false. If the 'bufsize' parameter is + specified, it specifies the size of the I/O buffers to/from the child + process.""" + _cleanup() + self.cmd = cmd + p2cread, p2cwrite = os.pipe() + c2pread, c2pwrite = os.pipe() + if capturestderr: + errout, errin = os.pipe() + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + if capturestderr: + os.dup2(errin, 2) + self._run_child(cmd) + os.close(p2cread) + self.tochild = os.fdopen(p2cwrite, 'w', bufsize) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) + if capturestderr: + os.close(errin) + self.childerr = os.fdopen(errout, 'r', bufsize) + else: + self.childerr = None + + def __del__(self): + # In case the child hasn't been waited on, check if it's done. + self.poll(_deadstate=sys.maxint) + if self.sts < 0: + if _active is not None: + # Child is still running, keep us alive until we can wait on it. + _active.append(self) + + def _run_child(self, cmd): + if isinstance(cmd, basestring): + cmd = ['/bin/sh', '-c', cmd] + os.closerange(3, MAXFD) + try: + os.execvp(cmd[0], cmd) + finally: + os._exit(1) + + def poll(self, _deadstate=None): + """Return the exit status of the child process if it has finished, + or -1 if it hasn't finished yet.""" + if self.sts < 0: + try: + pid, sts = os.waitpid(self.pid, os.WNOHANG) + # pid will be 0 if self.pid hasn't terminated + if pid == self.pid: + self.sts = sts + except os.error: + if _deadstate is not None: + self.sts = _deadstate + return self.sts + + def wait(self): + """Wait for and return the exit status of the child process.""" + if self.sts < 0: + pid, sts = os.waitpid(self.pid, 0) + # This used to be a test, but it is believed to be + # always true, so I changed it to an assertion - mvl + assert pid == self.pid + self.sts = sts + return self.sts + + +class Popen3Again: + """Class representing a child process. Normally, instances are created + internally by the functions popen2() and popen3().""" + + sts = -1 # Child not completed yet + + def __init__(self, cmd, capturestderr=False, bufsize=-1): + """The parameter 'cmd' is the shell command to execute in a + sub-process. On UNIX, 'cmd' may be a sequence, in which case arguments + will be passed directly to the program without shell intervention (as + with os.spawnv()). If 'cmd' is a string it will be passed to the shell + (as with os.system()). The 'capturestderr' flag, if true, specifies + that the object should capture standard error output of the child + process. The default is false. If the 'bufsize' parameter is + specified, it specifies the size of the I/O buffers to/from the child + process.""" + _cleanup() + self.cmd = cmd + p2cread, p2cwrite = os.pipe() + c2pread, c2pwrite = os.pipe() + if capturestderr: + errout, errin = os.pipe() + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + if capturestderr: + os.dup2(errin, 2) + self._run_child(cmd) + os.close(p2cread) + self.tochild = os.fdopen(p2cwrite, 'w', bufsize) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) + if capturestderr: + os.close(errin) + self.childerr = os.fdopen(errout, 'r', bufsize) + else: + self.childerr = None + + def __del__(self): + # In case the child hasn't been waited on, check if it's done. + self.poll(_deadstate=sys.maxint) + if self.sts < 0: + if _active is not None: + # Child is still running, keep us alive until we can wait on it. + _active.append(self) + + def _run_child(self, cmd): + if isinstance(cmd, basestring): + cmd = ['/bin/sh', '-c', cmd] + os.closerange(3, MAXFD) + try: + os.execvp(cmd[0], cmd) + finally: + os._exit(1) + + def poll(self, _deadstate=None): + """Return the exit status of the child process if it has finished, + or -1 if it hasn't finished yet.""" + if self.sts < 0: + try: + pid, sts = os.waitpid(self.pid, os.WNOHANG) + # pid will be 0 if self.pid hasn't terminated + if pid == self.pid: + self.sts = sts + except os.error: + if _deadstate is not None: + self.sts = _deadstate + return self.sts + + def wait(self): + """Wait for and return the exit status of the child process.""" + if self.sts < 0: + pid, sts = os.waitpid(self.pid, 0) + # This used to be a test, but it is believed to be + # always true, so I changed it to an assertion - mvl + assert pid == self.pid + self.sts = sts + return self.sts + +#Duplicate function with identifiers changed + +def dis3(y=None): + """frobnicate classes, methods, functions, or code. + + With no argument, frobnicate the last traceback. + + """ + if y is None: + distb() + return + if isinstance(y, types.InstanceType): + y = y.__class__ + if hasattr(y, 'im_func'): + y = y.im_func + if hasattr(y, 'func_code'): + y = y.func_code + if hasattr(y, '__dict__'): + items = y.__dict__.items() + items.sort() + for name, y1 in items: + if isinstance(y1, _have_code): + print("Disassembly of %s:" % name) + try: + dis(y1) + except TypeError(msg): + print("Sorry:", msg) + print() + elif hasattr(y, 'co_code'): + frobnicate(y) + elif isinstance(y, str): + frobnicate_string(y) + else: + raise TypeError( + "don't know how to frobnicate %s objects" % + type(y).__name__) + + +#Mostly similar function + +def dis4(x=None): + """Disassemble classes, methods, functions, or code. + + With no argument, disassemble the last traceback. + + """ + if x is None: + distb() + return + if isinstance(x, types.InstanceType): + x = x.__class__ + if hasattr(x, 'im_func'): + x = x.im_func + if hasattr(x, '__dict__'): + items = x.__dict__.items() + items.sort() + for name, x1 in items: + if isinstance(x1, _have_code): + print("Disassembly of %s:" % name) + try: + dis(x1) + except TypeError(msg): + print("Sorry:", msg) + print() + elif hasattr(x, 'co_code'): + disassemble(x) + elif isinstance(x, str): + disassemble_string(x) + else: + raise TypeError( + "don't know how to disassemble %s objects" % + type(x).__name__) + + +#Similar function with changed identifiers + +def dis5(z=None): + """splat classes, methods, functions, or code. + + With no argument, splat the last traceback. + + """ + if z is None: + distb() + return + if isinstance(z, types.InstanceType): + z = z.__class__ + if hasattr(z, 'im_func'): + z = z.im_func + if hasattr(y, 'func_code'): + y = y.func_code + if hasattr(z, '__dict__'): + items = z.__dict__.items() + items.sort() + for name, z1 in items: + if isinstance(z1, _have_code): + print("Disassembly of %s:" % name) + try: + dis(z1) + except TypeError(msg): + print("Sorry:", msg) + print() + elif hasattr(z, 'co_code'): + splat(z) + elif isinstance(z, str): + splat_string(z) + else: + raise TypeError( + "don't know how to splat %s objects" % + type(z).__name__) + + diff --git a/python/ql/test/query-tests/UselessCode/DuplicateCode/similar.py b/python/ql/test/query-tests/UselessCode/DuplicateCode/similar.py new file mode 100644 index 000000000000..8f36c5e65ed9 --- /dev/null +++ b/python/ql/test/query-tests/UselessCode/DuplicateCode/similar.py @@ -0,0 +1,63 @@ + + +def original(the_ast): + def walk(node, in_function, in_name_main): + def flags(): + return in_function * 2 + in_name_main + if isinstance(node, ast.Module): + for import_node in walk(node.body, in_function, in_name_main): + yield import_node + elif isinstance(node, ast.ImportFrom): + aliases = [ Alias(a.name, a.asname) for a in node.names] + yield FromImport(node.level, node.module, aliases, flags()) + elif isinstance(node, ast.Import): + aliases = [ Alias(a.name, a.asname) for a in node.names] + yield Import(aliases, flags()) + elif isinstance(node, ast.FunctionDef): + for _, child in ast.iter_fields(node): + for import_node in walk(child, True, in_name_main): + yield import_node + elif isinstance(node, list): + for n in node: + for import_node in walk(n, in_function, in_name_main): + yield import_node + return list(walk(the_ast, False, False)) + +def similar_1(the_ast): + def walk(node, in_function, in_name_main): + def flags(): + return in_function * 2 + in_name_main + if isinstance(node, ast.Module): + for import_node in walk(node.body, in_function, in_name_main): + yield import_node + elif isinstance(node, ast.ImportFrom): + aliases = [ Alias(a.name, a.asname) for a in node.names] + yield FromImport(node.level, node.module, aliases, flags()) + elif isinstance(node, ast.Import): + aliases = [ Alias(a.name, a.asname) for a in node.names] + yield Import(aliases, flags()) + elif isinstance(node, ast.FunctionDef): + for _, child in ast.iter_fields(node): + for import_node in walk(child, True, in_name_main): + yield import_node + return list(walk(the_ast, False, False)) + +def similar_2(the_ast): + def walk(node, in_function, in_name_main): + def flags(): + return in_function * 2 + in_name_main + if isinstance(node, ast.Module): + for import_node in walk(node.body, in_function, in_name_main): + yield import_node + elif isinstance(node, ast.Import): + aliases = [ Alias(a.name, a.asname) for a in node.names] + yield Import(aliases, flags()) + elif isinstance(node, ast.FunctionDef): + for _, child in ast.iter_fields(node): + for import_node in walk(child, True, in_name_main): + yield import_node + elif isinstance(node, list): + for n in node: + for import_node in walk(n, in_function, in_name_main): + yield import_node + return list(walk(the_ast, False, False)) diff --git a/python/ql/test/query-tests/Variables/capture/LoopVariableCapture.expected b/python/ql/test/query-tests/Variables/capture/LoopVariableCapture.expected new file mode 100644 index 000000000000..cc9ae7029d65 --- /dev/null +++ b/python/ql/test/query-tests/Variables/capture/LoopVariableCapture.expected @@ -0,0 +1,8 @@ +| test.py:5:9:5:20 | FunctionExpr | Capture of loop variable '$@' | test.py:4:5:4:23 | For | x | +| test.py:10:6:10:14 | Lambda | Capture of loop variable '$@' | test.py:10:5:10:36 | ListComp | i | +| test.py:42:6:42:14 | Lambda | Capture of loop variable '$@' | test.py:42:5:42:56 | ListComp | i | +| test.py:43:6:43:14 | Lambda | Capture of loop variable '$@' | test.py:43:5:43:56 | ListComp | j | +| test.py:45:6:45:14 | Lambda | Capture of loop variable '$@' | test.py:45:5:45:36 | SetComp | i | +| test.py:49:8:49:16 | Lambda | Capture of loop variable '$@' | test.py:49:5:49:38 | DictComp | i | +| test.py:57:6:57:14 | Lambda | Capture of loop variable '$@' | test.py:57:6:57:35 | GeneratorExp | i | +| test.py:62:10:62:18 | Lambda | Capture of loop variable '$@' | test.py:62:10:62:39 | GeneratorExp | i | diff --git a/python/ql/test/query-tests/Variables/capture/LoopVariableCapture.qlref b/python/ql/test/query-tests/Variables/capture/LoopVariableCapture.qlref new file mode 100644 index 000000000000..1e2a71cd6a71 --- /dev/null +++ b/python/ql/test/query-tests/Variables/capture/LoopVariableCapture.qlref @@ -0,0 +1 @@ +Variables/LoopVariableCapture.ql diff --git a/python/ql/test/query-tests/Variables/capture/test.py b/python/ql/test/query-tests/Variables/capture/test.py new file mode 100644 index 000000000000..480bc58862e0 --- /dev/null +++ b/python/ql/test/query-tests/Variables/capture/test.py @@ -0,0 +1,67 @@ + +def bad1(): + results = [] + for x in range(10): + def inner(): + return x + results.append(inner) + return results + +a = [lambda: i for i in range(1, 4)] +for f in a: + print(f()) + +#OK: +#Using default argument +def good1(): + results = [] + for y in range(10): + def inner(y=y): + return y + results.append(inner) + return results + +#Factory function +l = [] +for r in range(10): + def make_foo(r): + def foo(): + return r + return foo + l.append(make_foo(r)) + +#The inner function does not escape loop. +def ok1(): + result = 0 + for z in range(10): + def inner(): + return z + result += inner() + return result + +b = [lambda: i for i in range(1, 4) for j in range(1,5)] +c = [lambda: j for i in range(1, 4) for j in range(1,5)] + +s = {lambda: i for i in range(1, 4)} +for f in s: + print(f()) + +d = {i:lambda: i for i in range(1, 4)} +for k, f in d.items(): + print(k, f()) + +#Generator expressions are sometimes OK, if they evaluate the iteration +#When the captured variable is used. +#So technically this is a false positive, but it is extremely fragile +#code, so I (Mark) think it is fine to report it as a violation. +g = (lambda: i for i in range(1, 4)) +for f in g: + print(f()) + +#But not if evaluated eagerly +l = list(lambda: i for i in range(1, 4)) +for f in l: + print(f()) + +def odasa4860(asset_ids): + return dict((asset_id, filter(lambda c : c.asset_id == asset_id, xxx)) for asset_id in asset_ids) diff --git a/python/ql/test/query-tests/Variables/general/Global.expected b/python/ql/test/query-tests/Variables/general/Global.expected new file mode 100644 index 000000000000..073dea682edd --- /dev/null +++ b/python/ql/test/query-tests/Variables/general/Global.expected @@ -0,0 +1 @@ +| variables_test.py:64:5:64:14 | Global | Updating global variables except at module initialization is discouraged | diff --git a/python/ql/test/query-tests/Variables/general/Global.qlref b/python/ql/test/query-tests/Variables/general/Global.qlref new file mode 100644 index 000000000000..c20333a006e4 --- /dev/null +++ b/python/ql/test/query-tests/Variables/general/Global.qlref @@ -0,0 +1 @@ +Variables/Global.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Variables/general/GlobalAtModuleLevel.expected b/python/ql/test/query-tests/Variables/general/GlobalAtModuleLevel.expected new file mode 100644 index 000000000000..159e6954819b --- /dev/null +++ b/python/ql/test/query-tests/Variables/general/GlobalAtModuleLevel.expected @@ -0,0 +1 @@ +| variables_test.py:57:1:57:10 | Global | Declaring 'g_x' as global at module-level is redundant. | diff --git a/python/ql/test/query-tests/Variables/general/GlobalAtModuleLevel.qlref b/python/ql/test/query-tests/Variables/general/GlobalAtModuleLevel.qlref new file mode 100644 index 000000000000..f12469499b74 --- /dev/null +++ b/python/ql/test/query-tests/Variables/general/GlobalAtModuleLevel.qlref @@ -0,0 +1 @@ +Variables/GlobalAtModuleLevel.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Variables/general/ShadowBuiltin.expected b/python/ql/test/query-tests/Variables/general/ShadowBuiltin.expected new file mode 100644 index 000000000000..0035c96d0d78 --- /dev/null +++ b/python/ql/test/query-tests/Variables/general/ShadowBuiltin.expected @@ -0,0 +1 @@ +| variables_test.py:7:5:7:7 | len | Local variable len shadows a builtin variable. | diff --git a/python/ql/test/query-tests/Variables/general/ShadowBuiltin.qlref b/python/ql/test/query-tests/Variables/general/ShadowBuiltin.qlref new file mode 100644 index 000000000000..d732a539e5ff --- /dev/null +++ b/python/ql/test/query-tests/Variables/general/ShadowBuiltin.qlref @@ -0,0 +1 @@ +Variables/ShadowBuiltin.ql diff --git a/python/ql/test/query-tests/Variables/general/ShadowGlobal.expected b/python/ql/test/query-tests/Variables/general/ShadowGlobal.expected new file mode 100644 index 000000000000..ae59529932fe --- /dev/null +++ b/python/ql/test/query-tests/Variables/general/ShadowGlobal.expected @@ -0,0 +1 @@ +| variables_test.py:14:5:14:7 | sh1 | Local variable 'sh1' shadows a global variable defined $@. | variables_test.py:6:5:6:7 | sh1 | here | diff --git a/python/ql/test/query-tests/Variables/general/ShadowGlobal.qlref b/python/ql/test/query-tests/Variables/general/ShadowGlobal.qlref new file mode 100644 index 000000000000..d3d632da035a --- /dev/null +++ b/python/ql/test/query-tests/Variables/general/ShadowGlobal.qlref @@ -0,0 +1 @@ +Variables/ShadowGlobal.ql diff --git a/python/ql/test/query-tests/Variables/general/pytest/__init__.py b/python/ql/test/query-tests/Variables/general/pytest/__init__.py new file mode 100644 index 000000000000..8ab6454477d5 --- /dev/null +++ b/python/ql/test/query-tests/Variables/general/pytest/__init__.py @@ -0,0 +1,4 @@ +#Fake pytest module for testing. + +def fixture(param): + return param diff --git a/python/ql/test/query-tests/Variables/general/random_string.py b/python/ql/test/query-tests/Variables/general/random_string.py new file mode 100644 index 000000000000..287eae490578 --- /dev/null +++ b/python/ql/test/query-tests/Variables/general/random_string.py @@ -0,0 +1,17 @@ + +#ODASA-3688 +def generate_random_string(char_sequences, length): + random_string = "" + + # Two tests of the same variable to force splitting of the flow-graph + if char_sequences: + for char_seq in char_sequences: + pass + + def func_used(): + pass + + #Only use the variable on one of the split arms. + if char_sequences: + while len(random_string) < length: + random_string += func_used() diff --git a/python/ql/test/query-tests/Variables/general/variables_test.py b/python/ql/test/query-tests/Variables/general/variables_test.py new file mode 100644 index 000000000000..e623ee5244d6 --- /dev/null +++ b/python/ql/test/query-tests/Variables/general/variables_test.py @@ -0,0 +1,100 @@ + +__all__ = [ 'is_used_var1' ] + +#Shadow Builtin + +def sh1(x): + len = x + 2 #Shadows + len = x + 0 # no shadowing warning for 2nd def + return len + +#Shadow Global + +def sh2(x): + sh1 = x + 1 #Shadows + sh1 = x + 0 # no shadowing warning for 2nd def + return sh1 + +#This is OK +import module1 +def ok1(): + import module1 + +#Unused parameter, local and global + +def u1(x): + return 0 + +def u2(): + x = 1 + return 1 + +#These parameters are OK due to (potential overriding) +class C(object): + + @abstractmethod + def ok2(self, p): + pass + + def ok3(self, arg): + pass + +class D(C): + + def ok3(self, arg): + pass + +#Unused module variable +unused_var1 = 17 +unused_var2 = 18 +is_used_var1 = 19 +is_used_var2 = 20 + +def func(): + return is_used_var2 + +#Redundant global declaration +global g_x + +g_x = 0 + +#Use global + +def uses_global(arg): + global g_x + g_x = arg + +use(g_x) + +#Benign case of global shadowing: +if __name__ == "__main__": + #x is used as a local throughout this file + x = 0 + use(x) + +import pytest +#Shadow global in pytest.fixture: +@pytest.fixture +def the_fixture(): + pass + +#Use it +the_fixture(1) + +def test_function(the_fixture): + #Use it + return the_fixture + +#This is OK. ODASA-2908 +def odasa2908_global(g_x=g_x): + #Use arg cached in function object for speed + return g_x + +def odasa2908_builtin(arg, len=len): + #Use arg cached in function object for speed + return len(arg) + +#OK if marked as unused: +def ok_unused(unused_1): + unused_2 = 1 + return 0 diff --git a/python/ql/test/query-tests/Variables/multiple/MultiplyDefined.expected b/python/ql/test/query-tests/Variables/multiple/MultiplyDefined.expected new file mode 100644 index 000000000000..68d01f762f6f --- /dev/null +++ b/python/ql/test/query-tests/Variables/multiple/MultiplyDefined.expected @@ -0,0 +1,7 @@ +| uselesscode_test.py:4:5:4:8 | mult | This assignment to 'mult' is unnecessary as it is redefined $@ before this value is used. | uselesscode_test.py:15:5:15:8 | mult | here | +| uselesscode_test.py:5:5:5:5 | x | This assignment to 'x' is unnecessary as it is redefined $@ before this value is used. | uselesscode_test.py:7:5:7:5 | x | here | +| uselesscode_test.py:28:7:28:10 | Mult | This assignment to 'Mult' is unnecessary as it is redefined $@ before this value is used. | uselesscode_test.py:37:7:37:10 | Mult | here | +| uselesscode_test.py:52:9:52:11 | bad | This assignment to 'bad' is unnecessary as it is redefined $@ before this value is used. | uselesscode_test.py:53:9:53:11 | bad | here | +| uselesscode_test.py:67:9:67:11 | bad | This assignment to 'bad' is unnecessary as it is redefined $@ before this value is used. | uselesscode_test.py:71:9:71:11 | bad | here | +| uselesscode_test.py:117:5:117:5 | x | This assignment to 'x' is unnecessary as it is redefined $@ before this value is used. | uselesscode_test.py:118:5:118:5 | x | here | +| uselesscode_test.py:117:8:117:8 | y | This assignment to 'y' is unnecessary as it is redefined $@ before this value is used. | uselesscode_test.py:118:8:118:8 | y | here | diff --git a/python/ql/test/query-tests/Variables/multiple/MultiplyDefined.qlref b/python/ql/test/query-tests/Variables/multiple/MultiplyDefined.qlref new file mode 100644 index 000000000000..293098be566b --- /dev/null +++ b/python/ql/test/query-tests/Variables/multiple/MultiplyDefined.qlref @@ -0,0 +1 @@ +Variables/MultiplyDefined.ql diff --git a/python/ql/test/query-tests/Variables/multiple/uselesscode_test.py b/python/ql/test/query-tests/Variables/multiple/uselesscode_test.py new file mode 100644 index 000000000000..49f367d6db3e --- /dev/null +++ b/python/ql/test/query-tests/Variables/multiple/uselesscode_test.py @@ -0,0 +1,128 @@ + +#Multiple declarations + +def mult(a): + x = 1 + y = a + x = 2 + #Need to use x, otherwise it is ignored + #(The UnusedLocalVariable query will pick it up) + return x + +def unique(): + pass + +def mult(x,y): + pass + +#OK for multiple definition +import M +M = none + +def _double_loop(seq): + for i in seq: + pass + for i in seq: + pass + +class Mult(object): + + pass + +class C(object): + + def m(self): + pass + +class Mult(object): + pass + +### Tests inspired by real-world false positives +def isStr(s): + ok = '' + try: + ok += s + except TypeError: + return 0 + return 1 + +# 'bad' actually *is* always redefined before being read. +def have_nosmp(): + try: + bad = os.environ['NPY_NOSMP'] + bad = 1 + except KeyError: + bad = 0 + return bad + +def simple_try(foo): + try: + ok = foo.bar + except AttributeError: + ok = 'default' + return ok + +def try_with_else(foo): + try: + bad = foo.bar + except AttributeError: + raise + else: + bad = 'overwrite' + return bad + +# This should be fine +def append_all(xs): + global __doc__ + __doc__ += "all xs:" + for x in xs: + __doc__ += x + +def append_local(xs): + doc = "" + doc += "xs:" + for x in xs: + doc += x + return doc + +#ODASA-4100 +def odasa4100(name, language, options = ''): + distro_files = [] + if language == 'distro-cpp': + distro_files = [ "file" ] + if distro_files: + emit_odasa_deps() + #Flow-graph splitting will make this definition unused on the distro_files is True branch + env = '' + if distro_files: + env = 'env "ODASA_HOME=' + _top + '/' + distro_path + '" ' + emit_cmd(env + "some other stuff") + +#ODASA-4166 + +#This is OK as the first definition is a "declaration" +def odasa4166(cond): + x = None + if cond: + x = some_value() + else: + x = default_value() + return x + + +#ODASA-5315 +def odasa5315(): + x, y = foo() # OK as y is used + use(y) + x, y = bar() # Not OK as neither x nor y are used. + x, y = baz() # OK as both used + return x + y + +@multimethod(int) +def foo(i): + pass + +@multimethod(float) +def foo(f): + pass + diff --git a/python/ql/test/query-tests/Variables/undefined/UndefinedGlobal.expected b/python/ql/test/query-tests/Variables/undefined/UndefinedGlobal.expected new file mode 100644 index 000000000000..ed465bffae11 --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/UndefinedGlobal.expected @@ -0,0 +1,6 @@ +| UndefinedGlobal.py:22:5:22:7 | ug2 | This use of global variable 'ug2' may be undefined. | +| UndefinedGlobal.py:23:5:23:5 | e | This use of global variable 'e' may be undefined. | +| UndefinedGlobal.py:27:5:27:12 | __path__ | This use of global variable '__path__' may be undefined. | +| UndefinedGlobal.py:123:5:123:7 | ug3 | This use of global variable 'ug3' may be undefined. | +| UninitializedLocal.py:5:13:5:15 | ug1 | This use of global variable 'ug1' may be undefined. | +| UninitializedLocal.py:22:9:22:11 | ug1 | This use of global variable 'ug1' may be undefined. | diff --git a/python/ql/test/query-tests/Variables/undefined/UndefinedGlobal.py b/python/ql/test/query-tests/Variables/undefined/UndefinedGlobal.py new file mode 100644 index 000000000000..0438d8e84b3c --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/UndefinedGlobal.py @@ -0,0 +1,176 @@ +from ud_helper import d +import ud_helper as helper +from ud_helper import * +try: + import __builtin__ as B +except: + import builtins as B +defined = 2 +#Monkey patch some builtins +B.__dict__["monkey1"] = 1 +setattr(B, "monkey2", 2) +B.monkey3 = 3 + +def f(parameter): + parameter + local = 3 + local + d # Explicitly from ud_helper + helper # Explicitly as import + a # Imlicitly from ud_helper + defined + ug2 # ERROR + e # ERROR Defined in ud_helper, but not in __all__ + int + float + __file__ #OK all files have __file__ defined + __path__ #ERROR only modules have __path__ defined + + len #Ok defined in builtins + monkey1 #Ok monkey-patched builtins + monkey2 #Ok monkey-patched builtins + monkey3 #Ok monkey-patched builtins + +import sys +if __name__ == '__main__': + if len(sys.argv) == 1: + print('usage') + sys.exit(0) + elif ('-s' in sys.argv): + server = sys.argv[2] + else: + server = '127.0.0.1' + server + +#Check that builtins are always defined even if conditionally shadowed. +bool + +if d: + bool = int + +bool +def t2(): + return bool + +def f(): + + global local_global + local_global = 1 + + local_global + +#ODASA-2021 +from elsewhere import gflags, a_function + + +def usage_and_quit(): + print("Usage: unusable") + sys.exit(1) + + +def main(): + + global from_hdb + try: + # Because of from_hdb, get_mp_hashes.py has a lot of extra unique flags + gflags.DEFINE_bool("from_hdb", False, "") + + from_hdb = gflags.FLAGS.from_hdb + + except Exception as ex: + usage_and_quit(str(ex)) + + if not from_hdb: + a_function() + +#ODASA-2432 +def globals_guard(): + if 'varname' in globals(): + f(varname) + +#Example taken from logging module +try: + import module1 + import module2 +except ImportError: + module1 = None + + +if module1: + inst = module2.Class() + +#Some possible false positives, observed in ERP5. +if 1: + pfp1 = 1 + +pfp1 + +def outer(): + global pfp2 + pfp2 = 2 + def inner(): + global pfp2 + pfp2 += 1 + +class Cls(object): + global pfp3 + pfp3 = 3 + def inner(): + global pfp3 + pfp3 += 1 + +def only_report_once(): + ug3 + ug3 + ug3 + ug3 + +#ODASA-5381 +from _ast import * + +try: + NameConstant +except NameError: + NameConstant = Name + +#ODASA-5486 +class modinit: + + global _time + import time + _time = time + try: + import datetime + _pydatetime = datetime.datetime + except ImportError: + # Set them to (), so that isinstance() works with them + _pydatetime = () + +del modinit + +# FP occurs in line below +def _isstring(arg, isinstance=isinstance, t=_time): + pass + +#ODASA-4688 +def outer(): + def inner(): + global glob + glob = 'asdf' + print(glob[2]) + + def otherInner(): + print(glob[3]) + + inner() + + +#ODASA-5896 +guesses_made = 0 +while guesses_made < 6: # This loop is guaranteed to execute at least once. + guess = int(input('Take a guess: ')) + guesses_made += 1 + +if guess == 1017: # FP here + pass + diff --git a/python/ql/test/query-tests/Variables/undefined/UndefinedGlobal.qlref b/python/ql/test/query-tests/Variables/undefined/UndefinedGlobal.qlref new file mode 100644 index 000000000000..ea9f5a038426 --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/UndefinedGlobal.qlref @@ -0,0 +1 @@ +Variables/UndefinedGlobal.ql diff --git a/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.expected b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.expected new file mode 100644 index 000000000000..f2782e7e8889 --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.expected @@ -0,0 +1,14 @@ +| UninitializedLocal.py:13:16:13:17 | u2 | Local variable 'u2' may be used before it is initialized. | +| UninitializedLocal.py:19:16:19:17 | u3 | Local variable 'u3' may be used before it is initialized. | +| UninitializedLocal.py:37:12:37:13 | u4 | Local variable 'u4' may be used before it is initialized. | +| UninitializedLocal.py:46:5:46:6 | u6 | Local variable 'u6' may be used before it is initialized. | +| UninitializedLocal.py:69:5:69:6 | u8 | Local variable 'u8' may be used before it is initialized. | +| UninitializedLocal.py:75:5:75:6 | u9 | Local variable 'u9' may be used before it is initialized. | +| UninitializedLocal.py:85:5:85:7 | u11 | Local variable 'u11' may be used before it is initialized. | +| UninitializedLocal.py:88:9:88:11 | u12 | Local variable 'u12' may be used before it is initialized. | +| UninitializedLocal.py:119:16:119:18 | u14 | Local variable 'u14' may be used before it is initialized. | +| UninitializedLocal.py:151:16:151:18 | u19 | Local variable 'u19' may be used before it is initialized. | +| UninitializedLocal.py:163:7:163:7 | x | Local variable 'x' may be used before it is initialized. | +| UninitializedLocal.py:176:16:176:16 | x | Local variable 'x' may be used before it is initialized. | +| UninitializedLocal.py:178:16:178:16 | y | Local variable 'y' may be used before it is initialized. | +| odasa3987.py:11:8:11:10 | var | Local variable 'var' may be used before it is initialized. | diff --git a/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.py b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.py new file mode 100644 index 000000000000..f8a8d76ad15f --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.py @@ -0,0 +1,290 @@ + +class C: + + def m1(self): + y = ug1 + x = 1 + return y + + def m2(self, p): + return p + + def m3(self, x1): + return u2 + u2 = x1 + + def m4(self, x2): + if x2: + u3 = 1 + return u3 + +def f(): + y = ug1 + x = 1 + return y + +def g(x3): + def h(): + y = x3 + +def q(x4): + def h(): + y = x4 + x = 1 + +def j(u4): + del u4 + return u4 + +def k(x5): + x5 + 1 + del x5 + +def m(x6): + if x6: + u6 = 1 + u6 + #The following are not uninitialized, but unreachable. + u6 + u6 + +#ODASA-1897 +def l(x7): + try: + if f(): + raise Exception + mod_name = x7[:-3] + mod = __import__(mod_name) + except ImportError: + raise ValueError(mod_name) + + + +def check_del(cond): + u8 = 1 + if cond: + del u8 + else: + pass + u8 + if cond: + u9 = 1 + del u9 + else: + u9 = 2 + u9 + if cond: + x10 = 1 + del x10 + x10 = 2 + else: + x10 = 3 + x10 + u11 = 1 + del u11 + u11 + u12 = "hi" + del u12 + del u12 + +#x will always be defined. +def const_range(): + for i in range(4): + x = i + return x + + +def asserts_false(cond1, cond2): + if cond1: + x13 = 1 + elif cond2: + x13 = 2 + else: + assert False, "Can't happen" + return x13 + +#Splitting +def use_def_conditional(cond3): + if cond3: + x14 = 1 + x15 = 2 + if cond3: + return x14 + +def use_def_conditional(cond4, cond5): + if cond4: + u14 = 1 + x16 = 2 + if cond5: + return u14 + + +def init_and_set_flag_in_try(f): + try: + f() + x17 = 1 + success = True + except: + success = False + if success: + return x17 + +#Check that we can rely on splitting +def split_OK(): + try: + f() + x18 = 1 + cond = not True + except: + cond = not False + if not cond: + return x18 + +def split_not_OK(): + try: + f() + u19 = 1 + cond = not True + except: + cond = not False + if not not cond: + return u19 + +def double_is_none(x): + if x is not None: + v = 0 + if x is None: + return 0 + else: + return v + +#ODASA-4241 +def def_in_post_loop(seq): + j(x) + x = [] + for p in seq: + x = p + +#Check that we are consistent with both sides of if statement +#ODASA-4615 +def f(cond1, cond2): + if cond1: + x = 1 + else: + y = 1 + if cond2: + return x + else: + return y + +def needs_splitting(var): + if var: + other = 0 + if not var or other: #other looks like it might be undefined, but it is defined. + pass + +def odasa4867(status): + fail = (status != 200) + if not fail: + 1 + if not fail: + var = 2 + if not fail: + 3 + if not fail and not l(var): # Observed FP - var is defined. + 4 + fail = True # It is possible that this was interfering with splitting. + if not fail: # Not the same SSA variable as earlier + 5 + +def odasa5896(number): + guesses_made = 0 + while guesses_made < 6: # This loop is guaranteed to execute at least once. + guess = int(input('Take a guess: ')) + guesses_made += 1 + + if guess == number: # FP here + pass + +#ODASA 6212 +class C(object): + + def fail(self): + raise Exception() + +def may_fail(cond, c): + if cond: + x = 0 + else: + c.fail() + return x + + +def deliberate_name_error(cond): + if cond: + x = 0 + try: + x # x is uninitialised, but guarded. So don't report it. + except NameError: + x = 1 + return x # x is initialised here, but we would need splitting to know that. + + +from unknown import x +may_fail(x, C()) + + +def with_definition(x): + with open(x) as y: + y + +def multiple_defn_in_try(x): + try: + for p, q in x: + p + except KeyError: + pass + +# ODASA-6742 + +import sys +class X(object): + + def leave(self, code = 1): + sys.exit(code) + +class Y(object): + + def __init__(self, x): + self._x = x + + def leave(self): + self._x.leave() + +def odasa6742(cond, obj): + y = Y(X()) + try: + var = may_fail(cond, obj) + except: + y.leave() + var + + +#ODASA-6904 +def avoid_redundant_split(a): + if a: # Should split here + b = x() + else: + b = None + if b: # but not here, + pass + if b: # or here, because + pass + pass + try: # we want to split here + import foo + var = True + except: + var = False + if var: + foo.bar() #foo is defined here. diff --git a/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.qlref b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.qlref new file mode 100644 index 000000000000..f2d0e603554a --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.qlref @@ -0,0 +1 @@ +Variables/UninitializedLocal.ql diff --git a/python/ql/test/query-tests/Variables/undefined/import_star.py b/python/ql/test/query-tests/Variables/undefined/import_star.py new file mode 100644 index 000000000000..5bbf91718701 --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/import_star.py @@ -0,0 +1,7 @@ + +#ODASA-4596 +from tokens import * + +#TOKEN_1 is defined in tokens, but we cannot determine that statically. +TOKEN_1 + diff --git a/python/ql/test/query-tests/Variables/undefined/module1.py b/python/ql/test/query-tests/Variables/undefined/module1.py new file mode 100644 index 000000000000..bf08ecae9448 --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/module1.py @@ -0,0 +1,2 @@ +#If we use the standard import form the test runner will import quite a lot of the standard library +os = __import__("os") \ No newline at end of file diff --git a/python/ql/test/query-tests/Variables/undefined/odasa3987.py b/python/ql/test/query-tests/Variables/undefined/odasa3987.py new file mode 100644 index 000000000000..9c3a5fa5b2f3 --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/odasa3987.py @@ -0,0 +1,17 @@ + +from somwhere import may_raise, value, SomeException + +def f(cond1, cond2): + try: + may_raise() + var = value() + except Exception: + if cond2: + var = 7 + if var == 1: + var = var + 1 + elif var == 2: + var +- 3 + if cond2: + pass + var = var + 4 # var must be defined to have passed line 11 diff --git a/python/ql/test/query-tests/Variables/undefined/odasa6418.py b/python/ql/test/query-tests/Variables/undefined/odasa6418.py new file mode 100644 index 000000000000..958433c4287b --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/odasa6418.py @@ -0,0 +1,19 @@ + +import sys + +def bump_version(version): + try: + parts = map(int, version.split('.')) + except ValueError: + fail('Current version is not numeric') + parts[-1] += 1 + return '.'.join(map(str, parts)) + + +def fail(message): + print(message) + sys.exit(1) + +# To get the FP result reported in ODASA-6418, +#bump_version must be called (directly or transitively) from the module scope +bump_version() diff --git a/python/ql/test/query-tests/Variables/undefined/odasa6800.py b/python/ql/test/query-tests/Variables/undefined/odasa6800.py new file mode 100644 index 000000000000..b69d665c335c --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/odasa6800.py @@ -0,0 +1,9 @@ +#We don't (yet) follow this import +fail = __import__("odasa%s" % 6418).fail + +def foo(x): + if x: + var = 0 + else: + fail('Current version is not numeric') + return var diff --git a/python/ql/test/query-tests/Variables/undefined/options b/python/ql/test/query-tests/Variables/undefined/options new file mode 100644 index 000000000000..b91afde07678 --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=2 diff --git a/python/ql/test/query-tests/Variables/undefined/regression.py b/python/ql/test/query-tests/Variables/undefined/regression.py new file mode 100644 index 000000000000..1e2f12f28fd0 --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/regression.py @@ -0,0 +1,18 @@ +# Regression test for a false-positive in 'Uninitialized Local' + +def func(): + safe = 0 + for i in []: + safe = 1 + if True: + pass + print safe # wrongly flagged + +from module1 import * + +def func2(): + os + +def findPluginJars(dir): + return filter(lambda y: y, + (os.path.join(root, f) for root, _, files in os.walk(dir + '/plugins') for f in files)) diff --git a/python/ql/test/query-tests/Variables/undefined/tokens.py b/python/ql/test/query-tests/Variables/undefined/tokens.py new file mode 100644 index 000000000000..e3c7038eca6b --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/tokens.py @@ -0,0 +1,11 @@ +TOKEN_0 = 0 +TOKEN_1 = 1 +TOKEN_2 = 2 +TOKEN_3 = 3 +TOKEN_4 = 4 +TOKEN_5 = 5 + +__all__ = [ "TOKEN_0" ] + +for i in range(1,6): + __all__.append("TOKEN_%d" % i) diff --git a/python/ql/test/query-tests/Variables/undefined/ud_helper.py b/python/ql/test/query-tests/Variables/undefined/ud_helper.py new file mode 100644 index 000000000000..d4cab7a69496 --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/ud_helper.py @@ -0,0 +1,12 @@ + +def a(): pass + +def b(): pass + +def c(): pass + +def d(): pass + +def e(): pass + +__all__ = [ 'a', 'b', 'c' ] diff --git a/python/ql/test/query-tests/Variables/undefined/unknown_import.py b/python/ql/test/query-tests/Variables/undefined/unknown_import.py new file mode 100644 index 000000000000..217596ebee00 --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/unknown_import.py @@ -0,0 +1,8 @@ + +from who_knows_what import * + +#Anything could be imported from who_knows_what +#So we have to assume the following could be defined +a +b +c diff --git a/python/ql/test/query-tests/Variables/undefined/uses_exec.py b/python/ql/test/query-tests/Variables/undefined/uses_exec.py new file mode 100644 index 000000000000..8a78c8e46d14 --- /dev/null +++ b/python/ql/test/query-tests/Variables/undefined/uses_exec.py @@ -0,0 +1,8 @@ +from other import setup + +exec('x/_version.py') + +setup( + name='x', + version=__version__ + ) diff --git a/python/ql/test/query-tests/Variables/unused/SuspiciousUnusedLoopIterationVariable.expected b/python/ql/test/query-tests/Variables/unused/SuspiciousUnusedLoopIterationVariable.expected new file mode 100644 index 000000000000..2b104a610a14 --- /dev/null +++ b/python/ql/test/query-tests/Variables/unused/SuspiciousUnusedLoopIterationVariable.expected @@ -0,0 +1,6 @@ +| test.py:4:5:4:28 | For | For loop variable 't' is not used in the loop body. | +| test.py:66:9:66:26 | For | For loop variable 'y' is not used in the loop body. | +| test.py:72:5:72:22 | For | For loop variable 'y' is not used in the loop body. | +| test.py:78:5:78:22 | For | For loop variable 's' is not used in the loop body. | +| test.py:106:5:106:25 | For | For loop variable 'sess' is deleted, but not used, in the loop body. | +| variables_test.py:133:5:133:32 | For | For loop variable 'tag' is not used in the loop body. | diff --git a/python/ql/test/query-tests/Variables/unused/SuspiciousUnusedLoopIterationVariable.qlref b/python/ql/test/query-tests/Variables/unused/SuspiciousUnusedLoopIterationVariable.qlref new file mode 100644 index 000000000000..4b9f136451eb --- /dev/null +++ b/python/ql/test/query-tests/Variables/unused/SuspiciousUnusedLoopIterationVariable.qlref @@ -0,0 +1 @@ +Variables/SuspiciousUnusedLoopIterationVariable.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected b/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected new file mode 100644 index 000000000000..a902cad04cbc --- /dev/null +++ b/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected @@ -0,0 +1,5 @@ +| variables_test.py:29:5:29:5 | x | The value assigned to local variable 'x' is never used. | +| variables_test.py:89:5:89:5 | a | The value assigned to local variable 'a' is never used. | +| variables_test.py:89:7:89:7 | b | The value assigned to local variable 'b' is never used. | +| variables_test.py:89:9:89:9 | c | The value assigned to local variable 'c' is never used. | +| variables_test.py:95:5:95:7 | var | The value assigned to local variable 'var' is never used. | diff --git a/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.qlref b/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.qlref new file mode 100644 index 000000000000..bd6e5aaa069d --- /dev/null +++ b/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.qlref @@ -0,0 +1 @@ +Variables/UnusedLocalVariable.ql diff --git a/python/ql/test/query-tests/Variables/unused/UnusedModuleVariable.expected b/python/ql/test/query-tests/Variables/unused/UnusedModuleVariable.expected new file mode 100644 index 000000000000..248ce2d0637f --- /dev/null +++ b/python/ql/test/query-tests/Variables/unused/UnusedModuleVariable.expected @@ -0,0 +1,6 @@ +| variables_test.py:48:1:48:13 | not_used_var1 | The global variable 'not_used_var1' is not used. | +| variables_test.py:49:1:49:13 | not_used_var2 | The global variable 'not_used_var2' is not used. | +| variables_test.py:86:1:86:1 | a | The global variable 'a' is not used. | +| variables_test.py:86:3:86:3 | b | The global variable 'b' is not used. | +| variables_test.py:86:5:86:5 | c | The global variable 'c' is not used. | +| variables_test.py:100:1:100:8 | glob_var | The global variable 'glob_var' is not used. | diff --git a/python/ql/test/query-tests/Variables/unused/UnusedModuleVariable.qlref b/python/ql/test/query-tests/Variables/unused/UnusedModuleVariable.qlref new file mode 100644 index 000000000000..587ad9510764 --- /dev/null +++ b/python/ql/test/query-tests/Variables/unused/UnusedModuleVariable.qlref @@ -0,0 +1 @@ +Variables/UnusedModuleVariable.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Variables/unused/UnusedParameter.expected b/python/ql/test/query-tests/Variables/unused/UnusedParameter.expected new file mode 100644 index 000000000000..a5c9320e33ef --- /dev/null +++ b/python/ql/test/query-tests/Variables/unused/UnusedParameter.expected @@ -0,0 +1 @@ +| variables_test.py:25:1:25:10 | Function u1 | The parameter 'x' is never used. | diff --git a/python/ql/test/query-tests/Variables/unused/UnusedParameter.qlref b/python/ql/test/query-tests/Variables/unused/UnusedParameter.qlref new file mode 100644 index 000000000000..b37e4859c1b0 --- /dev/null +++ b/python/ql/test/query-tests/Variables/unused/UnusedParameter.qlref @@ -0,0 +1 @@ +Variables/UnusedParameter.ql diff --git a/python/ql/test/query-tests/Variables/unused/lazy_import.py b/python/ql/test/query-tests/Variables/unused/lazy_import.py new file mode 100644 index 000000000000..c838c77f06ba --- /dev/null +++ b/python/ql/test/query-tests/Variables/unused/lazy_import.py @@ -0,0 +1,7 @@ + +from clever_lazy_module_thing import range + +#OK iteration over range +def OK4(n): + for i in range(n): + print("x") diff --git a/python/ql/test/query-tests/Variables/unused/test.py b/python/ql/test/query-tests/Variables/unused/test.py new file mode 100644 index 000000000000..855e9a5ddfd0 --- /dev/null +++ b/python/ql/test/query-tests/Variables/unused/test.py @@ -0,0 +1,108 @@ + +#Unused +def fail(): + for t in [TypeA, TypeB]: + x = TypeA() + run_test(x) + +#OK by name +def OK1(seq): + for _ in seq: + do_something() + print("Hi") + +#OK counting +def OK2(seq): + i = 3 + for x in seq: + i += 1 + return i + +#OK check emptiness +def OK3(seq): + for thing in seq: + return "Not empty" + return "empty" + +#OK iteration over range +def OK4(n): + r = range(n) + for i in r: + print("x") + +#OK named as unused +def OK5(seq): + for unused_x in seq: + print("x") + +#ODASA-3794 +def OK6(seq): + for thing in seq: + if sum(1 for s in STATUSES + if thing <= s < thing + 100) >= quorum: + return True + +#OK -- Implicitly using count +def OK7(seq): + for x in seq: + queue.add(None) + +def OK8(seq): + for x in seq: + output.append("") + +#Likewise with parameters +def OK7(seq, queue): + for x in seq: + queue.add(None) + +def OK8(seq, output): + for x in seq: + output.append("") + +#Not OK -- Use a constant, but also a variable +def fail2(sequence): + for x in sequence: + for y in sequence: + do_something(x+1) + +def fail3(sequence): + for x in sequence: + do_something(x+1) + for y in sequence: + do_something(x+1) + +def fail4(coll, sequence): + while coll: + x = coll.pop() + for s in sequence: + do_something(x+1) + +#OK See ODASA-4153 and ODASA-4533 +def fail5(t): + x, y = t + return x + + +class OK9(object): + cls_attr = 0 + def __init__(self): + self.attr = self.cls_attr + +__all__ = [ 'hello' ] +__all__.extend(foo()) +maybe_defined_in_all = 17 + +#ODASA-5895 +def rand_list(): + return [ random.random() for i in range(100) ] + +def kwargs_is_a_use(seq): + for arg in seq: + func(**arg) + +#A deletion is a use, but this is almost certainly an error +def cleanup(sessions): + for sess in sessions: + # Original code had some comment about deleting sessions + del sess diff --git a/python/ql/test/query-tests/Variables/unused/variables_test.py b/python/ql/test/query-tests/Variables/unused/variables_test.py new file mode 100644 index 000000000000..6a30b7945a61 --- /dev/null +++ b/python/ql/test/query-tests/Variables/unused/variables_test.py @@ -0,0 +1,139 @@ + +__all__ = [ 'is_used_var1' ] + +__author__ = "Mark" + +__hidden_marker = False + + + + + + + + + + + + + + + + +#Unused parameter, local and global + +def u1(x): + return 0 + +def u2(): + x = 1 + return 1 + +#These parameters are OK due to (potential overriding) +class C(object): + + @abstractmethod + def ok2(self, p): + pass + + def ok3(self, arg): + pass + +class D(C): + + def ok3(self, arg): + pass + +#Unused module variable +not_used_var1 = 17 +not_used_var2 = 18 +is_used_var1 = 19 +is_used_var2 = 20 + +def func(): + return is_used_var2 + +#Redundant global declaration +global g_x + +g_x = 0 + +#Use global + +def uses_global(arg): + global g_x + g_x = arg + +use(g_x) + + +#Unused but with acceptable names +unused_var3 = g_x + +#Use at least one element of the tuple + +_a, _b, c = t + +use(c) + +def f(t): + _a, _b, c = t + use(c) + + +# Entirely unused tuple + +a,b,c = t + +def f(t): + a,b,c = t + use(t) + +def second_def_undefined(): + var = 0 + use(var) + var = 1 # unused. + +#And gloablly +glob_var = 0 +use(glob_var) +glob_var = 1 # unused + + + + +#OK if marked as unused: +def ok_unused(unused_1): + unused_2 = 1 + return 0 + +#Decorators count as a use: + +@compound.decorator() +def _unused_but_decorated(): + pass + +def decorated_inner_function(): + + @flasklike.routing("/route") + def end_point(): + pass + + @complex.decorator("x") + class C(object): + pass + + return 0 + + + +#FP observed https://lgtm.com/projects/g/torchbox/wagtail/alerts/ +def test_dict_unpacking(queryset, field_name, value): + #True positive + for tag in value.split(','): + queryset = queryset.filter(**{field_name + '__name': tag1}) + return queryset + #False positive + for tag in value.split(','): + queryset = queryset.filter(**{field_name + '__name': tag}) + return queryset diff --git a/python/ql/test/query-tests/analysis/Sanity/Sanity.expected b/python/ql/test/query-tests/analysis/Sanity/Sanity.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/analysis/Sanity/Sanity.qlref b/python/ql/test/query-tests/analysis/Sanity/Sanity.qlref new file mode 100644 index 000000000000..692b9cacd4e0 --- /dev/null +++ b/python/ql/test/query-tests/analysis/Sanity/Sanity.qlref @@ -0,0 +1 @@ +analysis/Sanity.ql diff --git a/python/ql/test/query-tests/analysis/Sanity/test.py b/python/ql/test/query-tests/analysis/Sanity/test.py new file mode 100644 index 000000000000..7877ec789fe1 --- /dev/null +++ b/python/ql/test/query-tests/analysis/Sanity/test.py @@ -0,0 +1,36 @@ + + +class BaseClass(object): + + cls_attr = 0 + + def __init__(self): + self.shadowing = 2 + +class DerivedClass(BaseClass): + + cls_attr = 3 + shadowing = 5 + + def __init__(self): + BaseClass.__init__(self) + self.inst_attr = 4 + + def method(self): + self.cls_attr + self.inst_attr + self.shadowing + +#ODASA-3836 +def comprehensions_and_generators(seq): + [y*y for y in seq] + (y*y for y in seq) + {y*y for y in seq} + {y:y*y for y in seq} + +#ODASA-5391 +@decorator(x) +class Decorated(object): + pass + +d = Decorated() diff --git a/python/ql/test/query-tests/analysis/jump_to_defn/Definitions.expected b/python/ql/test/query-tests/analysis/jump_to_defn/Definitions.expected new file mode 100644 index 000000000000..1162bce43fa0 --- /dev/null +++ b/python/ql/test/query-tests/analysis/jump_to_defn/Definitions.expected @@ -0,0 +1,28 @@ +| test.py:8:9:8:12 | self | test.py:7:18:7:21 | Definition test.py:7 | Definition | +| test.py:10:20:10:28 | BaseClass | test.py:3:1:3:24 | Definition test.py:3 | Definition | +| test.py:16:9:16:17 | BaseClass | test.py:3:1:3:24 | Definition test.py:3 | Definition | +| test.py:16:19:16:26 | Attribute | test.py:7:5:7:23 | Definition test.py:7 | Definition | +| test.py:16:28:16:31 | self | test.py:15:18:15:21 | Definition test.py:15 | Definition | +| test.py:17:9:17:12 | self | test.py:15:18:15:21 | Definition test.py:15 | Definition | +| test.py:20:9:20:12 | self | test.py:19:16:19:19 | Definition test.py:19 | Definition | +| test.py:20:14:20:21 | Attribute | test.py:12:16:12:16 | Definition test.py:12 | Definition | +| test.py:21:9:21:12 | self | test.py:19:16:19:19 | Definition test.py:19 | Definition | +| test.py:21:14:21:22 | Attribute | test.py:17:9:17:22 | Definition test.py:17 | Definition | +| test.py:22:9:22:12 | self | test.py:19:16:19:19 | Definition test.py:19 | Definition | +| test.py:22:14:22:22 | Attribute | test.py:13:17:13:17 | Definition test.py:13 | Definition | +| test.py:26:19:26:21 | seq | test.py:25:35:25:37 | Definition test.py:25 | Definition | +| test.py:27:19:27:21 | seq | test.py:25:35:25:37 | Definition test.py:25 | Definition | +| test.py:28:19:28:21 | seq | test.py:25:35:25:37 | Definition test.py:25 | Definition | +| test.py:29:21:29:23 | seq | test.py:25:35:25:37 | Definition test.py:25 | Definition | +| test.py:36:5:36:13 | Decorated | test.py:32:2:32:13 | Definition test.py:32 | Definition | +| test.py:38:8:38:13 | ImportExpr | module.py:0:0:0:0 | Definition module.py:0 | Definition | +| test.py:39:6:39:11 | ImportExpr | module.py:0:0:0:0 | Definition module.py:0 | Definition | +| test.py:39:20:39:22 | ImportMember | module.py:1:7:1:7 | Definition module.py:1 | Definition | +| test.py:40:1:40:3 | foo | test.py:39:20:39:22 | Definition test.py:39 | Definition | +| test.py:41:1:41:5 | thing | test.py:38:8:38:13 | Definition test.py:38 | Definition | +| test.py:41:7:41:9 | Attribute | module.py:2:7:2:7 | Definition module.py:2 | Definition | +| test.py:43:6:43:12 | ImportExpr | package/__init__.py:0:0:0:0 | Definition package/__init__.py:0 | Definition | +| test.py:43:21:43:21 | ImportMember | package/__init__.py:2:18:2:18 | Definition package/__init__.py:2 | Definition | +| test.py:44:8:44:14 | ImportExpr | package/__init__.py:0:0:0:0 | Definition package/__init__.py:0 | Definition | +| test.py:45:1:45:1 | p | test.py:44:8:44:14 | Definition test.py:44 | Definition | +| test.py:45:3:45:3 | Attribute | package/__init__.py:2:18:2:18 | Definition package/__init__.py:2 | Definition | diff --git a/python/ql/test/query-tests/analysis/jump_to_defn/Definitions.qlref b/python/ql/test/query-tests/analysis/jump_to_defn/Definitions.qlref new file mode 100644 index 000000000000..d4e89a35c97f --- /dev/null +++ b/python/ql/test/query-tests/analysis/jump_to_defn/Definitions.qlref @@ -0,0 +1 @@ +analysis/Definitions.ql diff --git a/python/ql/test/query-tests/analysis/jump_to_defn/module.py b/python/ql/test/query-tests/analysis/jump_to_defn/module.py new file mode 100644 index 000000000000..618f37ebbb40 --- /dev/null +++ b/python/ql/test/query-tests/analysis/jump_to_defn/module.py @@ -0,0 +1,2 @@ +foo = 1 +bar = 2 diff --git a/python/ql/test/query-tests/analysis/jump_to_defn/package/__init__.py b/python/ql/test/query-tests/analysis/jump_to_defn/package/__init__.py new file mode 100644 index 000000000000..e91fc44d9bad --- /dev/null +++ b/python/ql/test/query-tests/analysis/jump_to_defn/package/__init__.py @@ -0,0 +1,2 @@ + +from .mod import x diff --git a/python/ql/test/query-tests/analysis/jump_to_defn/package/mod.py b/python/ql/test/query-tests/analysis/jump_to_defn/package/mod.py new file mode 100644 index 000000000000..6e5a9d6cc26f --- /dev/null +++ b/python/ql/test/query-tests/analysis/jump_to_defn/package/mod.py @@ -0,0 +1,2 @@ + +x = 1 diff --git a/python/ql/test/query-tests/analysis/jump_to_defn/test.py b/python/ql/test/query-tests/analysis/jump_to_defn/test.py new file mode 100644 index 000000000000..0abfeb32c401 --- /dev/null +++ b/python/ql/test/query-tests/analysis/jump_to_defn/test.py @@ -0,0 +1,45 @@ + + +class BaseClass(object): + + cls_attr = 0 + + def __init__(self): + self.shadowing = 2 + +class DerivedClass(BaseClass): + + cls_attr = 3 + shadowing = 5 + + def __init__(self): + BaseClass.__init__(self) + self.inst_attr = 4 + + def method(self): + self.cls_attr + self.inst_attr + self.shadowing + +#ODASA-3836 +def comprehensions_and_generators(seq): + [y*y for y in seq] + (y*y for y in seq) + {y*y for y in seq} + {y:y*y for y in seq} + +#ODASA-5391 +@decorator(x) +class Decorated(object): + pass + +d = Decorated() + +import module as thing +from module import foo +foo +thing.bar + +from package import x +import package as p +p.x diff --git a/python/ql/test/query-tests/analysis/pointsto/CallGraphEfficiency.expected b/python/ql/test/query-tests/analysis/pointsto/CallGraphEfficiency.expected new file mode 100644 index 000000000000..a3d7def63d25 --- /dev/null +++ b/python/ql/test/query-tests/analysis/pointsto/CallGraphEfficiency.expected @@ -0,0 +1,2 @@ +| 0 | 29 | 29 | 100.0 | +| 1 | 4 | 40 | 10.0 | diff --git a/python/ql/test/query-tests/analysis/pointsto/CallGraphEfficiency.qlref b/python/ql/test/query-tests/analysis/pointsto/CallGraphEfficiency.qlref new file mode 100644 index 000000000000..6543a3753179 --- /dev/null +++ b/python/ql/test/query-tests/analysis/pointsto/CallGraphEfficiency.qlref @@ -0,0 +1 @@ +analysis/CallGraphEfficiency.ql diff --git a/python/ql/test/query-tests/analysis/pointsto/CallGraphMarginalEfficiency.expected b/python/ql/test/query-tests/analysis/pointsto/CallGraphMarginalEfficiency.expected new file mode 100644 index 000000000000..0d3950e7a1e5 --- /dev/null +++ b/python/ql/test/query-tests/analysis/pointsto/CallGraphMarginalEfficiency.expected @@ -0,0 +1,2 @@ +| 0 | 29 | 29 | 100.0 | +| 1 | 3 | 40 | 7.5 | diff --git a/python/ql/test/query-tests/analysis/pointsto/CallGraphMarginalEfficiency.qlref b/python/ql/test/query-tests/analysis/pointsto/CallGraphMarginalEfficiency.qlref new file mode 100644 index 000000000000..90a59409cd65 --- /dev/null +++ b/python/ql/test/query-tests/analysis/pointsto/CallGraphMarginalEfficiency.qlref @@ -0,0 +1 @@ +analysis/CallGraphMarginalEfficiency.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/analysis/pointsto/FailedInference.expected b/python/ql/test/query-tests/analysis/pointsto/FailedInference.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/analysis/pointsto/FailedInference.qlref b/python/ql/test/query-tests/analysis/pointsto/FailedInference.qlref new file mode 100644 index 000000000000..b08adfa00b57 --- /dev/null +++ b/python/ql/test/query-tests/analysis/pointsto/FailedInference.qlref @@ -0,0 +1 @@ +analysis/FailedInference.ql diff --git a/python/ql/test/query-tests/analysis/pointsto/KeyPointsToFailure.expected b/python/ql/test/query-tests/analysis/pointsto/KeyPointsToFailure.expected new file mode 100644 index 000000000000..197ffae7992a --- /dev/null +++ b/python/ql/test/query-tests/analysis/pointsto/KeyPointsToFailure.expected @@ -0,0 +1,33 @@ +| test.py:30:13:30:21 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:37:21:37:29 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:43:9:43:17 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:50:17:50:25 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:52:13:52:21 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:55:13:55:21 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:65:43:65:51 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:118:43:118:55 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:129:24:129:32 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:133:24:133:32 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:137:24:137:32 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:141:24:141:32 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:155:16:155:30 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:229:17:229:27 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:230:18:230:31 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:422:16:422:29 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:443:21:443:34 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:445:18:445:31 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:454:21:454:34 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:460:20:460:33 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:461:18:461:31 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:470:20:470:33 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:476:20:476:33 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:477:20:477:33 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:509:16:509:33 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:531:21:531:29 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:645:35:645:48 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:656:39:656:52 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:663:20:663:28 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:676:23:676:33 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:686:30:686:53 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:712:37:712:49 | Attribute | Expression does not 'point-to' any object, but all its sources do. | +| test.py:712:55:712:63 | Attribute | Expression does not 'point-to' any object, but all its sources do. | diff --git a/python/ql/test/query-tests/analysis/pointsto/KeyPointsToFailure.qlref b/python/ql/test/query-tests/analysis/pointsto/KeyPointsToFailure.qlref new file mode 100644 index 000000000000..db945187917b --- /dev/null +++ b/python/ql/test/query-tests/analysis/pointsto/KeyPointsToFailure.qlref @@ -0,0 +1 @@ +analysis/KeyPointsToFailure.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/analysis/pointsto/Pruned.expected b/python/ql/test/query-tests/analysis/pointsto/Pruned.expected new file mode 100644 index 000000000000..53854800e6f2 --- /dev/null +++ b/python/ql/test/query-tests/analysis/pointsto/Pruned.expected @@ -0,0 +1 @@ +| 1418 | diff --git a/python/ql/test/query-tests/analysis/pointsto/Pruned.qlref b/python/ql/test/query-tests/analysis/pointsto/Pruned.qlref new file mode 100644 index 000000000000..2d2c28dbc261 --- /dev/null +++ b/python/ql/test/query-tests/analysis/pointsto/Pruned.qlref @@ -0,0 +1 @@ +analysis/Pruned.ql diff --git a/python/ql/test/query-tests/analysis/pointsto/test.py b/python/ql/test/query-tests/analysis/pointsto/test.py new file mode 100644 index 000000000000..655af2bd4e0f --- /dev/null +++ b/python/ql/test/query-tests/analysis/pointsto/test.py @@ -0,0 +1,723 @@ + +class SmallSet(list): + + __slots__ = [] + + def update(self, other): + filtered = [x for x in other if x not in self] + self.extend(filtered) + + def add(self, item): + if item not in self: + self.append(item) + +class DiGraph(object): + '''A simple directed graph class (not necessarily a DAG). + Nodes must be hashable''' + + def __init__(self, name = ""): + self.name = name + self.pred = {} + self.succ = {} + self.all_nodes = [] + self.node_annotations = {} + self.edge_annotations = {} + + def add_node(self, n): + 'Add a node to the graph' + if n not in self.succ: + self.pred[n] = SmallSet() + self.succ[n] = SmallSet() + self.all_nodes.append(n) + + def add_edge(self, x, y): + '''Add an edge (x -> y) to the graph. Return true if x, y was + previously in graph''' + if x in self.succ: + if y in self.succ[x]: + return True + else: + self.add_node(x) + self.add_node(y) + self.pred[y].add(x) + self.succ[x].add(y) + return False + + def remove_node(self, x): + if x not in self.succ: + raise ValueError("Node %s does not exist." % x) + preds = self.pred[x] + succs = self.succ[x] + for p in preds: + self.succ[p].remove(x) + for s in succs: + self.pred[s].remove(x) + del self.succ[x] + del self.pred[x] + + def remove_edge(self, x, y): + self.pred[y].remove(x) + self.succ[x].remove(y) + + def annotate_edge(self, x, y, note): + '''Set the annotation on the edge (x -> y) to note. + ''' + if x not in self.succ or y not in self.succ[x]: + raise ValueError("Edge %s -> %s does not exist." % (x, y)) + self.edge_annotations[(x,y)] = note + + def annotate_node(self, x, note): + '''Set the annotation on the node x to note. + ''' + if x not in self.succ: + raise ValueError("Node %s does not exist." % x) + self.node_annotations[x] = note + + def nodes(self): + '''Return an iterator for all nodes, in the form (node, note) pairs. + Do not modify the graph while using this iterator''' + for node in self.all_nodes: + yield node, self.node_annotations.get(node) + + def edges(self): + '''Return an iterator for all edges, in the form of (pred, succ, note) triple. + Do not modify the graph while using this iterator''' + index = dict((n, i) for i, n in enumerate(self.all_nodes)) + for n in self.all_nodes: + n_succs = self.succ[n] + for succ in sorted(n_succs, key = lambda n : index[n]): + yield n, succ, self.edge_annotations.get((n,succ)) + + def sources(self): + '''Return an iterator for all nodes with no predecessors. + Do not modify the graph while using this iterator''' + for n, p in self.pred.items(): + if not p: + yield n + + def __contains__(self, node): + return node in self.succ + + +class FlowGraph(DiGraph): + '''A DiGraph that supports the concept of definitions and variables. + Used to compute dominance and SSA form. + For more explanation of the algorithms used see + 'Modern Compiler Implementation by Andrew W. Appel. + ''' + + def __init__(self, root, name): + DiGraph.__init__(self, name) + self.definitions = {} + self.deletions = {} + self.uses = {} + self.use_all_nodes = set() + self.root = root + + def clear_computed(self): + to_be_deleted = [attr for attr in self.__dict__ if attr[0] == '_'] + for attr in to_be_deleted: + delattr(self, attr) + + def _require(self, what): + '''Ensures that 'what' has been computed (computing if needed).''' + if hasattr(self, "_" + what): + return + setattr(self, "_" + what, getattr(self, "_compute_" + what)()) + + def add_deletion(self, node, var): + assert node in self.succ + self.deletions[node] = var + + def add_definition(self, node, var): + assert node in self.succ + self.definitions[node] = var + + def add_use(self, node, var): + assert node in self.succ, node + self.uses[node] = var + + def use_all_defined_variables(self, node): + assert node in self.succ + self.use_all_nodes.add(node) + + def _compute_depth_first_pre_order(self): + self._require("depth_first_pre_order_labels") + reachable = [ f for f in self.all_nodes if f in self._depth_first_pre_order_labels ] + return sorted(reachable, key = lambda f : -self._depth_first_pre_order_labels[f]) + + def _compute_reachable(self): + self._require("depth_first_pre_order") + return frozenset(self._depth_first_pre_order) + + def reachable_nodes(self): + self._require("reachable") + return self._reachable + + def _compute_reversed_depth_first_pre_order(self): + self._require("depth_first_pre_order") + return reversed(self._depth_first_pre_order) + + def _compute_bb_depth_first_pre_order(self): + self._require('depth_first_pre_order') + self._require('bb_heads') + bbs = [] + for n in self._depth_first_pre_order: + if n in self._bb_heads: + bbs.append(n) + return bbs + + def _compute_bb_reversed_depth_first_pre_order(self): + self._require("bb_depth_first_pre_order") + return reversed(self._bb_depth_first_pre_order) + + def _compute_depth_first_pre_order_labels(self): + 'Compute order with depth first search.' + orders = {} + order = 0 + nodes_to_visit = [ self.root ] + while nodes_to_visit: + node = nodes_to_visit[-1] + orders[node] = 0 + if node in self.succ: + for succ in self.succ[node]: + if succ not in orders: + nodes_to_visit.append(succ) + else: + order += 1 + orders[node] = order + if node is nodes_to_visit[-1]: + nodes_to_visit.pop() + order += 1 + orders[node] = order + return orders + + def _compute_idoms(self): + self._require("depth_first_pre_order") + idoms = {} + + def idom_intersection(n1, n2): + 'Determine the last common idom of n1, n2' + orders = self._depth_first_pre_order_labels + while n1 is not n2: + while orders[n1] < orders[n2]: + n1 = idoms[n1] + while orders[n2] < orders[n1]: + n2 = idoms[n2] + return n1 + + for node in self._depth_first_pre_order: + if len(self.pred[node]) == 1: + idoms[node] = next(iter(self.pred[node])) + else: + idom = None + for p in self.pred[node]: + if p == self.root: + idom = p + elif p in idoms: + if idom is None: + idom = p + else: + idom = idom_intersection(idom, p) + if idom is not None: + idoms[node] = idom + return idoms + + def idoms(self): + '''Returns an iterable of node pairs: node, idom(node)''' + self._require('idoms') + idoms = self._idoms + for n in self.all_nodes: + if n in idoms: + yield n, idoms[n] + + + def _compute_dominance_frontier(self): + '''Compute the dominance frontier: + DF[n] = DF_local[n] Union over C in children DF_up[c]''' + + def dominates(dom, node): + while node in idoms: + next_node = idoms[node] + if dom == next_node: + return True + node = next_node + return False + + self._require('idoms') + idoms = self._idoms + dominance_frontier = {} + df_up = {} + dom_tree = _reverse_map(idoms) + self._require('reversed_depth_first_pre_order') + for node in self._reversed_depth_first_pre_order: + df_local_n = set(n for n in self.succ[node] if node != idoms[n]) + dfn = df_local_n + if node in dom_tree: + for child in dom_tree[node]: + dfn.update(df_up[child]) + dominance_frontier[node] = dfn + if node in idoms: + imm_dom = idoms[node] + df_up[node] = set(n for n in dfn if not dominates(imm_dom, n)) + else: + df_up[node] = dfn + return dominance_frontier + + def _compute_phi_nodes(self): + '''Compute the phi nodes for this graph. + A minimal set of phi-nodes are computed; + No phi-nodes are added unless the variable is live. + ''' + self._require('dominance_frontier') + self._require('liveness') + dominance_frontier = self._dominance_frontier + definitions = dict(self.definitions) + # We must count deletions as definitions here. Otherwise, we can have + # uses of a deleted variable whose SSA definition is an actual definition, + # rather than a deletion. + definitions.update(self.deletions) + phi_nodes = {} + defsites = {} + for a in definitions.values(): + defsites[a] = set() + for n in definitions: + a = definitions[n] + defsites[a].add(n) + for a in defsites: + W = set(defsites[a]) + while W: + n = W.pop() + if n not in dominance_frontier: + continue + for y in dominance_frontier[n]: + if y not in phi_nodes: + phi_nodes[y] = set() + if a not in phi_nodes[y]: + phi_nodes[y].add(a) + if y not in definitions or a != definitions[y]: + W.add(y) + trimmed = {} + for node in phi_nodes: + assert node in self._bb_heads + if node not in self._liveness: + continue + new_phi_vars = set() + phi_vars = phi_nodes[node] + for v in phi_vars: + if v in self._liveness[node]: + new_phi_vars.add(v) + if new_phi_vars: + trimmed[node] = new_phi_vars + return trimmed + + def _compute_ssa_data(self): + ''' Compute the SSA variables, definitions, uses and phi-inputs. + ''' + self._require('basic_blocks') + self._require('phi_nodes') + self._require('bb_depth_first_pre_order') + self._require('use_all') + phi_nodes = self._phi_nodes + reaching_ssa_vars = {} + work_set = set() + work_set.add(self.root) + ssa_defns = {} + ssa_uses = {} + ssa_phis = {} + ssa_vars = set() + ssa_var_cache = {} + + def make_ssa_var(variable, node): + '''Ensure that there is no more than one SSA variable for each (variable, node) pair.''' + uid = (variable, node) + if uid in ssa_var_cache: + return ssa_var_cache[uid] + var = SSA_Var(variable, node) + ssa_var_cache[uid] = var + return var + + for bb in self._bb_depth_first_pre_order: + #Track SSA variables in each BB. + reaching_ssa_vars[bb] = {} + for bb in self._bb_depth_first_pre_order: + live_vars = reaching_ssa_vars[bb].copy() + #Add an SSA definition for each phi-node. + if bb in phi_nodes: + variables = phi_nodes[bb] + for v in variables: + var = make_ssa_var(v, bb) + ssa_defns[var] = bb + live_vars[v] = var + for node in self.nodes_in_bb(bb): + #Add an SSA use for each use. + if node in self.uses: + a = self.uses[node] + if a not in live_vars: + #Treat a use as adding a reaching variable, + #since a second use, if it can be reached, + #will always find the variable defined. + var = make_ssa_var(a, node) + live_vars[a] = var + else: + var = live_vars[a] + ssa_vars.add(var) + ssa_uses[node] = [ var ] + #Add an SSA use for all live SSA variables for + #each use_all (end of module/class scope). + if node in self._use_all: + all_live = [ var for var in live_vars.values() if var.variable in self._use_all[node]] + ssa_uses[node] = all_live + ssa_vars.update(all_live) + #Add an SSA definition for each definition. + if node in self.definitions: + a = self.definitions[node] + var = make_ssa_var(a, node) + ssa_defns[var] = node + live_vars[a] = var + #Although deletions are not definitions, we treat them as such. + #SSA form has no concept of deletion, so we have to treat `del x` + #as `x = Undefined`. + if node in self.deletions: + a = self.deletions[node] + if a in live_vars: + var = live_vars[a] + ssa_vars.add(var) + ssa_uses[node] = [ var ] + else: + #If no var is defined here we don't need to create one + #as a new one will be immediately be defined by the deletion. + pass + var = make_ssa_var(a, node) + ssa_defns[var] = node + live_vars[a] = var + #Propagate set of reaching variables to + #successor blocks. + for n in self.succ[node]: + reaching_ssa_vars[n].update(live_vars) + if n in phi_nodes: + for v in phi_nodes[n]: + if v in live_vars: + var = make_ssa_var(v, n) + if var not in ssa_phis: + ssa_phis[var] = set() + ssa_vars.add(live_vars[v]) + ssa_phis[var].add(live_vars[v]) + #Prune unused definitions. + used_ssa_defns = {} + for var in ssa_defns: + if var in ssa_vars: + used_ssa_defns[var] = ssa_defns[var] + ssa_defns = used_ssa_defns + sorted_vars = list(self._sort_ssa_variables(ssa_vars)) + assert set(sorted_vars) == ssa_vars + assert len(sorted_vars) == len(ssa_vars) + ssa_vars = sorted_vars + return ssa_vars, ssa_defns, ssa_uses, ssa_phis + + + def ssa_variables(self): + '''Returns all the SSA variables for this graph''' + self._require('ssa_data') + return self._ssa_data[0] + + def _sort_ssa_variables(self, ssa_vars): + node_to_var = {} + for v in ssa_vars: + node = v.node + if node in node_to_var: + vset = node_to_var[node] + else: + vset = set() + node_to_var[node] = vset + vset.add(v) + for n in self.all_nodes: + if n in node_to_var: + variables = node_to_var[n] + for v in sorted(variables, key=lambda v:v.variable.id): + yield v + + def ssa_definitions(self): + '''Returns all the SSA definition as an iterator of (node, variable) pairs.''' + self._require('ssa_data') + ssa_defns = self._ssa_data[1] + reversed_defns = _reverse_map(ssa_defns) + for n in self.all_nodes: + if n in reversed_defns: + variables = reversed_defns[n] + for v in sorted(variables, key=lambda v:v.variable.id): + yield n, v + + def get_ssa_definition(self, var): + '''Returns the definition node of var. Returns None if there is no definition.''' + self._require('ssa_data') + ssa_defns = self._ssa_data[1] + return ssa_defns.get(var) + + def ssa_uses(self): + '''Returns all the SSA uses as an iterator of (node, variable) pairs.''' + self._require('ssa_data') + ssa_uses = self._ssa_data[2] + for n in self.all_nodes: + if n in ssa_uses: + variables = ssa_uses[n] + for v in sorted(variables, key=lambda v:v.variable.id): + yield n, v + + def get_ssa_variables_used(self, node): + '''Returns all the SSA variables used at this node''' + self._require('ssa_data') + ssa_uses = self._ssa_data[2] + return ssa_uses.get(node, ()) + + def ssa_phis(self): + '''Return all SSA phi inputs as an iterator of (variable, input-variable) pairs.''' + self._require('ssa_data') + ssa_phis = self._ssa_data[3] + ssa_vars = self._ssa_data[0] + indexed = dict((v, index) for index, v in enumerate(ssa_vars)) + for v in ssa_vars: + if v not in ssa_phis: + continue + phis = ssa_phis[v] + for phi in sorted(phis, key=lambda v:indexed[v]): + yield v, phi + + def _compute_bb_heads(self): + '''Compute all flow nodes that are the first node in a basic block.''' + bb_heads = set() + for node in self.all_nodes: + preds = self.pred[node] + if len(preds) != 1 or len(self.succ[preds[0]]) != 1: + bb_heads.add(node) + return bb_heads + + def _compute_basic_blocks(self): + '''Compute Basic blocks membership''' + self._require('bb_heads') + basic_blocks = {} + bb_tails = {} + for bb in self._bb_heads: + for index, node in enumerate(self.nodes_in_bb(bb)): + basic_blocks[node] = bb, index + bb_tails[bb] = node + self._bb_tails = bb_tails + return basic_blocks + + def get_basic_blocks(self): + self._require('basic_blocks') + return self._basic_blocks + + def _compute_bb_succ(self): + self._require('basic_blocks') + bb_succs = {} + for bb in self._bb_heads: + bb_succs[bb] = self.succ[self._bb_tails[bb]] + return bb_succs + + def _compute_bb_pred(self): + self._require('basic_blocks') + bb_preds = {} + for bb in self._bb_heads: + preds_of_bb = self.pred[bb] + bb_preds[bb] = SmallSet(self._basic_blocks[p][0] for p in preds_of_bb) + return bb_preds + + def nodes_in_bb(self, bb): + '''Return an iterator over all node in basic block 'bb.''' + node = bb + while True: + yield node + succs = self.succ[node] + if not succs: + return + node = succs[0] + if node in self._bb_heads: + return + + + def _compute_use_all(self): + '''Compute which variables have been defined. + A variable is defined at node n, if there is a path to n which + passes through a definition, but not through a subsequent deletion. + ''' + + self._require('bb_heads') + self._require('bb_succ') + self._require('bb_pred') + use_all = {} + + def defined_in_block(bb): + defined = defined_at_start[bb].copy() + for node in self.nodes_in_bb(bb): + if node in self.definitions: + var = self.definitions[node] + defined.add(var) + if node in self.deletions: + var = self.deletions[node] + defined.discard(var) + if node in self.use_all_nodes: + use_all[node] = frozenset(defined) + return defined + + defined_at_start = {} + work_set = set() + for bb in self._bb_heads: + if not self._bb_pred[bb]: + work_set.add(bb) + defined_at_start[bb] = set() + work_list = list(work_set) + while work_list: + bb = work_list.pop() + work_set.remove(bb) + defined_at_bb_end = defined_in_block(bb) + for succ in self._bb_succ[bb]: + if succ not in defined_at_start: + defined_at_start[succ] = set() + elif defined_at_start[succ] >= defined_at_bb_end: + continue + defined_at_start[succ].update(defined_at_bb_end) + if succ not in work_set: + work_list.append(succ) + work_set.add(succ) + return use_all + + def _compute_liveness(self): + '''Compute liveness of all variables in this flow-graph. + Return a mapping of basic blocks to the set of variables + that are live at the start of that basic block. + See http://en.wikipedia.org/wiki/Live_variable_analysis.''' + + self._require('bb_pred') + self._require('use_all') + + def gen_and_kill_for_block(bb): + gen = set() + kill = set() + for node in reversed(list(self.nodes_in_bb(bb))): + if node in self.uses: + var = self.uses[node] + gen.add(var) + kill.discard(var) + if node in self.deletions: + var = self.deletions[node] + gen.add(var) + kill.discard(var) + if node in self.definitions: + var = self.definitions[node] + gen.discard(var) + kill.add(var) + if node in self._use_all: + for var in self._use_all[node]: + gen.add(var) + kill.discard(var) + return gen, kill + + def liveness_for_block(bb, live_out): + return gens[bb].union(live_out.difference(kills[bb])) + + live_at_end = {} + live_at_start = {} + gens = {} + kills = {} + work_set = set() + #Initialise + for bb in self._bb_heads: + gens[bb], kills[bb] = gen_and_kill_for_block(bb) + live_at_end[bb] = set() + live_at_start[bb] = set() + work_set.add(bb) + #Find fixed point + while work_set: + bb = work_set.pop() + live_in = liveness_for_block(bb, live_at_end[bb]) + if live_in != live_at_start[bb]: + assert len(live_in) > len(live_at_start[bb]) + live_at_start[bb] = live_in + for pred in self._bb_pred[bb]: + work_set.add(pred) + live_at_end[pred] = live_at_end[pred].union(live_in) + return live_at_start + + + def delete_unreachable_nodes(self): + self._require("reachable") + unreachable = [u for u in self.all_nodes if u not in self._reachable] + if not unreachable: + return + for mapping in (self.definitions, self.deletions, self.uses): + for u in unreachable: + if u in mapping: + del mapping[u] + for u in unreachable: + self.use_all_nodes.discard(u) + self.remove_node(u) + #Make sure we retain the order of all_nodes. + self.all_nodes = [r for r in self.all_nodes if r in self._reachable] + self.clear_computed() + + def dominated_by(self, node): + self._require('idoms') + assert node in self, str(node) + " is not in graph" + dominated = set([node]) + todo = set(self.succ[node]) + while todo: + n = todo.pop() + if n in dominated: + continue + #Unreachable nodes will not be in self._idoms + if n in self._idoms and self._idoms[n] in dominated: + dominated.add(n) + todo.update(self.succ[n]) + return dominated + + def strictly_dominates(self, pre, post): + self._require('idoms') + while post in self._idoms: + post = self._idoms[post] + if pre == post: + return True + return False + + def reaches_while_dominated(self, pre, post, control): + ''' Holds if `pre` reaches `post` while remaining in the + region dominated by `control`.''' + self._require('dominance_frontier') + dominance_frontier = self._dominance_frontier[control] + todo = { pre } + reached = set() + while todo: + node = todo.pop() + if node in dominance_frontier: + continue + if node == post: + return True + if node in reached: + continue + reached.add(node) + todo.update(self.succ[node]) + return False + + +class SSA_Var(object): + 'A single static assignment variable' + + __slots__ = [ 'variable', 'node' ] + + def __init__(self, variable, node): + self.variable = variable + self.node = node + + def __repr__(self): + return 'SSA_Var(%r, %r)' % (self.variable.id, self.node) + + +def _reverse_map(mapping): + 'Reverse a mapping of keys -> values to value->set(keys)' + inv_map = {} + for k, v in mapping.items(): + if v not in inv_map: + inv_map[v] = SmallSet() + inv_map[v].add(k) + return inv_map + diff --git a/python/ql/test/query-tests/analysis/suppression/AlertSuppression.expected b/python/ql/test/query-tests/analysis/suppression/AlertSuppression.expected new file mode 100644 index 000000000000..d74b38438727 --- /dev/null +++ b/python/ql/test/query-tests/analysis/suppression/AlertSuppression.expected @@ -0,0 +1,59 @@ +| test.py:4:4:4:9 | Comment # lgtm | lgtm | lgtm | test.py:4:1:4:9 | suppression range | +| test.py:5:4:5:27 | Comment # lgtm[py/line-too-long] | lgtm[py/line-too-long] | lgtm[py/line-too-long] | test.py:5:1:5:27 | suppression range | +| test.py:6:4:6:51 | Comment # lgtm[py/line-too-long, py/non-callable-called] | lgtm[py/line-too-long, py/non-callable-called] | lgtm[py/line-too-long, py/non-callable-called] | test.py:6:1:6:51 | suppression range | +| test.py:7:4:7:24 | Comment # lgtm[@tag:security] | lgtm[@tag:security] | lgtm[@tag:security] | test.py:7:1:7:24 | suppression range | +| test.py:8:4:8:41 | Comment # lgtm[@tag:security,py/line-too-long] | lgtm[@tag:security,py/line-too-long] | lgtm[@tag:security,py/line-too-long] | test.py:8:1:8:41 | suppression range | +| test.py:9:4:9:30 | Comment # lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | test.py:9:1:9:30 | suppression range | +| test.py:10:4:10:65 | Comment # lgtm[py/non-callable-called] because I know better than lgtm | lgtm[py/non-callable-called] because I know better than lgtm | lgtm[py/non-callable-called] | test.py:10:1:10:65 | suppression range | +| test.py:11:4:11:20 | Comment # lgtm: blah blah | lgtm: blah blah | lgtm | test.py:11:1:11:20 | suppression range | +| test.py:12:4:12:34 | Comment # lgtm blah blah #falsepositive | lgtm blah blah #falsepositive | lgtm | test.py:12:1:12:34 | suppression range | +| test.py:13:4:13:36 | Comment # lgtm blah blah -- falsepositive | lgtm blah blah -- falsepositive | lgtm | test.py:13:1:13:36 | suppression range | +| test.py:14:4:14:34 | Comment #lgtm [py/non-callable-called] | lgtm [py/non-callable-called] | lgtm [py/non-callable-called] | test.py:14:1:14:34 | suppression range | +| test.py:15:4:15:11 | Comment # lgtm[] | lgtm[] | lgtm[] | test.py:15:1:15:11 | suppression range | +| test.py:17:4:17:8 | Comment #lgtm | lgtm | lgtm | test.py:17:1:17:8 | suppression range | +| test.py:18:4:18:12 | Comment # lgtm | lgtm | lgtm | test.py:18:1:18:12 | suppression range | +| test.py:19:4:19:31 | Comment # lgtm [py/line-too-long] | lgtm [py/line-too-long] | lgtm [py/line-too-long] | test.py:19:1:19:31 | suppression range | +| test.py:20:4:20:14 | Comment # lgtm lgtm | lgtm lgtm | lgtm | test.py:20:1:20:14 | suppression range | +| test.py:27:12:27:23 | Comment #lgtm [func] | lgtm [func] | lgtm [func] | test.py:27:1:27:23 | suppression range | +| test.py:29:17:29:35 | Comment # lgtm on docstring | lgtm on docstring | lgtm | test.py:29:1:29:35 | suppression range | +| test.py:30:16:30:47 | Comment #lgtm [py/duplicate-key-in-dict] | lgtm [py/duplicate-key-in-dict] | lgtm [py/duplicate-key-in-dict] | test.py:30:1:30:47 | suppression range | +| test.py:35:10:35:21 | Comment # lgtm class | lgtm class | lgtm | test.py:35:1:35:21 | suppression range | +| test.py:36:21:36:33 | Comment # lgtm method | lgtm method | lgtm | test.py:36:1:36:33 | suppression range | +| test.py:39:4:39:8 | Comment #noqa | noqa | lgtm | test.py:39:1:39:8 | suppression range | +| test.py:40:4:40:9 | Comment # noqa | noqa | lgtm | test.py:40:1:40:9 | suppression range | +| test.py:50:34:50:117 | Comment # noqa: E501; (line too long) pylint: disable=invalid-name; lgtm [py/missing-equals] | noqa: E501; (line too long) pylint: disable=invalid-name; lgtm [py/missing-equals] | lgtm [py/missing-equals] | test.py:50:1:50:117 | suppression range | +| test.py:52:4:52:67 | Comment # noqa: E501; (line too long) pylint: disable=invalid-name; lgtm | noqa: E501; (line too long) pylint: disable=invalid-name; lgtm | lgtm | test.py:52:1:52:67 | suppression range | +| test.py:53:4:53:78 | Comment # random nonsense lgtm [py/missing-equals] and then some more commentary... | random nonsense lgtm [py/missing-equals] and then some more commentary... | lgtm [py/missing-equals] | test.py:53:1:53:78 | suppression range | +| test.py:58:4:58:9 | Comment # LGTM | LGTM | LGTM | test.py:58:1:58:9 | suppression range | +| test.py:59:4:59:27 | Comment # LGTM[py/line-too-long] | LGTM[py/line-too-long] | LGTM[py/line-too-long] | test.py:59:1:59:27 | suppression range | +| test.py:65:4:65:60 | Comment # lgtm[py/line-too-long] and lgtm[py/non-callable-called] | lgtm[py/line-too-long] and lgtm[py/non-callable-called] | lgtm[py/line-too-long] | test.py:65:1:65:60 | suppression range | +| test.py:65:4:65:60 | Comment # lgtm[py/line-too-long] and lgtm[py/non-callable-called] | lgtm[py/line-too-long] and lgtm[py/non-callable-called] | lgtm[py/non-callable-called] | test.py:65:1:65:60 | suppression range | +| test.py:66:4:66:33 | Comment # lgtm[py/line-too-long]; lgtm | lgtm[py/line-too-long]; lgtm | lgtm | test.py:66:1:66:33 | suppression range | +| test.py:66:4:66:33 | Comment # lgtm[py/line-too-long]; lgtm | lgtm[py/line-too-long]; lgtm | lgtm[py/line-too-long] | test.py:66:1:66:33 | suppression range | +| testWindows.py:4:4:4:9 | Comment # lgtm | lgtm | lgtm | testWindows.py:4:1:4:9 | suppression range | +| testWindows.py:5:4:5:27 | Comment # lgtm[py/line-too-long] | lgtm[py/line-too-long] | lgtm[py/line-too-long] | testWindows.py:5:1:5:27 | suppression range | +| testWindows.py:6:4:6:51 | Comment # lgtm[py/line-too-long, py/non-callable-called] | lgtm[py/line-too-long, py/non-callable-called] | lgtm[py/line-too-long, py/non-callable-called] | testWindows.py:6:1:6:51 | suppression range | +| testWindows.py:7:4:7:24 | Comment # lgtm[@tag:security] | lgtm[@tag:security] | lgtm[@tag:security] | testWindows.py:7:1:7:24 | suppression range | +| testWindows.py:8:4:8:41 | Comment # lgtm[@tag:security,py/line-too-long] | lgtm[@tag:security,py/line-too-long] | lgtm[@tag:security,py/line-too-long] | testWindows.py:8:1:8:41 | suppression range | +| testWindows.py:9:4:9:30 | Comment # lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | testWindows.py:9:1:9:30 | suppression range | +| testWindows.py:10:4:10:65 | Comment # lgtm[py/non-callable-called] because I know better than lgtm | lgtm[py/non-callable-called] because I know better than lgtm | lgtm[py/non-callable-called] | testWindows.py:10:1:10:65 | suppression range | +| testWindows.py:11:4:11:20 | Comment # lgtm: blah blah | lgtm: blah blah | lgtm | testWindows.py:11:1:11:20 | suppression range | +| testWindows.py:12:4:12:34 | Comment # lgtm blah blah #falsepositive | lgtm blah blah #falsepositive | lgtm | testWindows.py:12:1:12:34 | suppression range | +| testWindows.py:13:4:13:36 | Comment # lgtm blah blah -- falsepositive | lgtm blah blah -- falsepositive | lgtm | testWindows.py:13:1:13:36 | suppression range | +| testWindows.py:14:4:14:34 | Comment #lgtm [py/non-callable-called] | lgtm [py/non-callable-called] | lgtm [py/non-callable-called] | testWindows.py:14:1:14:34 | suppression range | +| testWindows.py:15:4:15:11 | Comment # lgtm[] | lgtm[] | lgtm[] | testWindows.py:15:1:15:11 | suppression range | +| testWindows.py:17:4:17:8 | Comment #lgtm | lgtm | lgtm | testWindows.py:17:1:17:8 | suppression range | +| testWindows.py:18:4:18:12 | Comment # lgtm | lgtm | lgtm | testWindows.py:18:1:18:12 | suppression range | +| testWindows.py:19:4:19:31 | Comment # lgtm [py/line-too-long] | lgtm [py/line-too-long] | lgtm [py/line-too-long] | testWindows.py:19:1:19:31 | suppression range | +| testWindows.py:20:4:20:14 | Comment # lgtm lgtm | lgtm lgtm | lgtm | testWindows.py:20:1:20:14 | suppression range | +| testWindows.py:27:12:27:23 | Comment #lgtm [func] | lgtm [func] | lgtm [func] | testWindows.py:27:1:27:23 | suppression range | +| testWindows.py:29:17:29:35 | Comment # lgtm on docstring | lgtm on docstring | lgtm | testWindows.py:29:1:29:35 | suppression range | +| testWindows.py:30:16:30:47 | Comment #lgtm [py/duplicate-key-in-dict] | lgtm [py/duplicate-key-in-dict] | lgtm [py/duplicate-key-in-dict] | testWindows.py:30:1:30:47 | suppression range | +| testWindows.py:35:10:35:21 | Comment # lgtm class | lgtm class | lgtm | testWindows.py:35:1:35:21 | suppression range | +| testWindows.py:36:21:36:33 | Comment # lgtm method | lgtm method | lgtm | testWindows.py:36:1:36:33 | suppression range | +| testWindows.py:39:3:39:7 | Comment #noqa | noqa | lgtm | testWindows.py:39:1:39:7 | suppression range | +| testWindows.py:40:4:40:9 | Comment # noqa | noqa | lgtm | testWindows.py:40:1:40:9 | suppression range | +| testWindows.py:48:4:48:60 | Comment # lgtm[py/line-too-long] and lgtm[py/non-callable-called] | lgtm[py/line-too-long] and lgtm[py/non-callable-called] | lgtm[py/line-too-long] | testWindows.py:48:1:48:60 | suppression range | +| testWindows.py:48:4:48:60 | Comment # lgtm[py/line-too-long] and lgtm[py/non-callable-called] | lgtm[py/line-too-long] and lgtm[py/non-callable-called] | lgtm[py/non-callable-called] | testWindows.py:48:1:48:60 | suppression range | +| testWindows.py:49:4:49:33 | Comment # lgtm[py/line-too-long]; lgtm | lgtm[py/line-too-long]; lgtm | lgtm | testWindows.py:49:1:49:33 | suppression range | +| testWindows.py:49:4:49:33 | Comment # lgtm[py/line-too-long]; lgtm | lgtm[py/line-too-long]; lgtm | lgtm[py/line-too-long] | testWindows.py:49:1:49:33 | suppression range | diff --git a/python/ql/test/query-tests/analysis/suppression/AlertSuppression.qlref b/python/ql/test/query-tests/analysis/suppression/AlertSuppression.qlref new file mode 100644 index 000000000000..7837430fbf92 --- /dev/null +++ b/python/ql/test/query-tests/analysis/suppression/AlertSuppression.qlref @@ -0,0 +1 @@ +analysis/AlertSuppression.ql diff --git a/python/ql/test/query-tests/analysis/suppression/test.py b/python/ql/test/query-tests/analysis/suppression/test.py new file mode 100644 index 000000000000..17c495ff1a44 --- /dev/null +++ b/python/ql/test/query-tests/analysis/suppression/test.py @@ -0,0 +1,66 @@ + +# Formatting tests: + +"" # lgtm +"" # lgtm[py/line-too-long] +"" # lgtm[py/line-too-long, py/non-callable-called] +"" # lgtm[@tag:security] +"" # lgtm[@tag:security,py/line-too-long] +"" # lgtm[@expires:2017-06-11] +"" # lgtm[py/non-callable-called] because I know better than lgtm +"" # lgtm: blah blah +"" # lgtm blah blah #falsepositive +"" # lgtm blah blah -- falsepositive +"" #lgtm [py/non-callable-called] +"" # lgtm[] +"" # lgtmfoo +"" #lgtm +"" # lgtm +"" # lgtm [py/line-too-long] +"" # lgtm lgtm + + +#lgtm -- Ignore this -- No line or scope. + +#On real code: + +def foo(): #lgtm [func] + # lgtm -- Blank line (ignore for now, maybe scope wide in future). + "docstring" # lgtm on docstring + return { #lgtm [py/duplicate-key-in-dict] + "a": 1, + "a": 2 + } + +class C: # lgtm class + def meth(self): # lgtm method + pass + +"" #noqa +"" # noqa + +"The following should be ignored" +"" # flake8: noqa +"" # noqa: F401 +"" # noqa -- Some extra detail. +"" #Ignore + +#Suppression for multiple tools +#LGTM-1929 +class frozenbidict(BidictBase): # noqa: E501; (line too long) pylint: disable=invalid-name; lgtm [py/missing-equals] + pass +"" # noqa: E501; (line too long) pylint: disable=invalid-name; lgtm +"" # random nonsense lgtm [py/missing-equals] and then some more commentary... + + +# Case insensitive comments + +"" # LGTM +"" # LGTM[py/line-too-long] + +#Avoid some erroneous matches +"" # foolgtm[py/missing-equals] +"" # foolgtm + +"" # lgtm[py/line-too-long] and lgtm[py/non-callable-called] +"" # lgtm[py/line-too-long]; lgtm diff --git a/python/ql/test/query-tests/analysis/suppression/testWindows.py b/python/ql/test/query-tests/analysis/suppression/testWindows.py new file mode 100644 index 000000000000..50c8f8a5aae4 --- /dev/null +++ b/python/ql/test/query-tests/analysis/suppression/testWindows.py @@ -0,0 +1,49 @@ + +# Formatting tests: + +"" # lgtm +"" # lgtm[py/line-too-long] +"" # lgtm[py/line-too-long, py/non-callable-called] +"" # lgtm[@tag:security] +"" # lgtm[@tag:security,py/line-too-long] +"" # lgtm[@expires:2017-06-11] +"" # lgtm[py/non-callable-called] because I know better than lgtm +"" # lgtm: blah blah +"" # lgtm blah blah #falsepositive +"" # lgtm blah blah -- falsepositive +"" #lgtm [py/non-callable-called] +"" # lgtm[] +"" # lgtmfoo +"" #lgtm +"" # lgtm +"" # lgtm [py/line-too-long] +"" # lgtm lgtm + + +#lgtm -- Ignore this -- No line or scope. + +#On real code: + +def foo(): #lgtm [func] + # lgtm -- Blank line (ignore for now, maybe scope wide in future). + "docstring" # lgtm on docstring + return { #lgtm [py/duplicate-key-in-dict] + "a": 1, + "a": 2 + } + +class C: # lgtm class + def meth(self): # lgtm method + pass + +""#noqa +"" # noqa + +"The following should be ignored" +# flake8: noqa +# noqa: F401 +# noqa -- Some extra detail. +#Ignore + +"" # lgtm[py/line-too-long] and lgtm[py/non-callable-called] +"" # lgtm[py/line-too-long]; lgtm diff --git a/python/ql/test/query-tests/options b/python/ql/test/query-tests/options new file mode 100644 index 000000000000..bd016eb1ce56 --- /dev/null +++ b/python/ql/test/query-tests/options @@ -0,0 +1,2 @@ +automatic_locations: true +semmle-extractor-options: --max-import-depth=1