Merge pull request #3770 from tausbn/python-add-a-bunch-of-documentation

Python: Add a bunch of documentation.
This commit is contained in:
Rasmus Wriedt Larsen
2020-06-26 13:30:45 +02:00
committed by GitHub
20 changed files with 340 additions and 106 deletions

View File

@@ -1,3 +1,5 @@
/** INTERNAL - Methods used by queries that test whether functions are invoked correctly. */
import python
import Testing.Mox
@@ -71,36 +73,38 @@ private int positional_arg_count_for_call(Call call, Value callable) {
)
}
/** Gets the number of arguments in `call`. */
int arg_count_objectapi(Call call) {
result = count(call.getAnArg()) + varargs_length_objectapi(call) + count(call.getAKeyword())
}
/** Gets the number of arguments in `call`. */
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*/
/** Gets a call corresponding to the given class or function. */
private ControlFlowNode get_a_call_objectapi(Object callable) {
result = callable.(ClassObject).getACall()
or
result = callable.(FunctionObject).getACall()
}
/* Gets a call corresponding to the given class or function*/
/** Gets a call corresponding to the given class or function. */
private ControlFlowNode get_a_call(Value callable) {
result = callable.(ClassValue).getACall()
or
result = callable.(FunctionValue).getACall()
}
/* Gets the function object corresponding to the given class or function*/
/** Gets the function object corresponding to the given class or function. */
FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) {
result = func_or_cls.(FunctionObject)
or
result = func_or_cls.(ClassObject).declaredAttribute("__init__")
}
/* Gets the function object corresponding to the given class or function*/
/** Gets the function object corresponding to the given class or function. */
FunctionValue get_function_or_initializer(Value func_or_cls) {
result = func_or_cls.(FunctionValue)
or

View File

@@ -1,5 +1,6 @@
import python
/** A string constant that looks like it may be used in string formatting operations. */
library class PossibleAdvancedFormatString extends StrConst {
PossibleAdvancedFormatString() { this.getText().matches("%{%}%") }
@@ -51,6 +52,7 @@ library class PossibleAdvancedFormatString extends StrConst {
predicate isExplicitlyNumbered() { exists(this.fieldId(_, _).toInt()) }
}
/** Holds if the formatting string `fmt` contains a sequence of braces `{` of length `len`, beginning at index `index`. */
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
@@ -61,10 +63,12 @@ predicate brace_sequence(PossibleAdvancedFormatString fmt, int index, int len) {
)
}
/** Holds if index `index` in the format string `fmt` contains an escaped brace `{`. */
predicate escaped_brace(PossibleAdvancedFormatString fmt, int index) {
exists(int len | brace_sequence(fmt, index, len) | len % 2 = 0)
}
/** Holds if index `index` in the format string `fmt` contains a left brace `{` that acts as an escape character. */
predicate escaping_brace(PossibleAdvancedFormatString fmt, int index) {
escaped_brace(fmt, index + 1)
}
@@ -105,15 +109,18 @@ private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatS
)
}
/** A string constant that has the `format` method applied to it. */
class AdvancedFormatString extends PossibleAdvancedFormatString {
AdvancedFormatString() { advanced_format_call(_, this, _) }
}
/** A string formatting operation that uses the `format` method. */
class AdvancedFormattingCall extends Call {
AdvancedFormattingCall() { advanced_format_call(this, _, _) }
/** Count of the arguments actually provided */
int providedArgCount() { advanced_format_call(this, _, result) }
/** Gets a formatting string for this call. */
AdvancedFormatString getAFormat() { advanced_format_call(this, result, _) }
}

View File

@@ -1,5 +1,8 @@
/** INTERNAL - Helper predicates for queries that inspect the comparison of objects using `is`. */
import python
/** Holds if the comparison `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */
predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) {
exists(CompareNode fcomp | fcomp = comp.getAFlowNode() |
fcomp.operands(left, op, right) and
@@ -7,6 +10,7 @@ predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, Cont
)
}
/** Holds if the class `c` overrides the default notion of equality or comparison. */
predicate overrides_eq_or_cmp(ClassValue c) {
major_version() = 2 and c.hasAttribute("__eq__")
or
@@ -19,12 +23,14 @@ predicate overrides_eq_or_cmp(ClassValue c) {
major_version() = 2 and c.hasAttribute("__cmp__")
}
/** Holds if the class `cls` is likely to only have a single instance throughout the program. */
predicate probablySingleton(ClassValue cls) {
strictcount(Value inst | inst.getClass() = cls) = 1
or
cls = Value::named("None").getClass()
}
/** Holds if using `is` to compare instances of the class `c` is likely to cause unexpected behavior. */
predicate invalid_to_use_is_portably(ClassValue 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__
@@ -35,6 +41,7 @@ predicate invalid_to_use_is_portably(ClassValue c) {
not probablySingleton(c)
}
/** Holds if the control flow node `f` points to either `True`, `False`, or `None`. */
predicate simple_constant(ControlFlowNode f) {
exists(Value val | f.pointsTo(val) |
val = Value::named("True") or val = Value::named("False") or val = Value::named("None")
@@ -66,10 +73,12 @@ private predicate universally_interned_value(Expr e) {
e.(StrConst).getText() = ""
}
/** Holds if the expression `e` points to an interned constant in CPython. */
predicate cpython_interned_constant(Expr e) {
exists(Expr const | e.pointsTo(_, const) | cpython_interned_value(const))
}
/** Holds if the expression `e` points to a value that can be reasonably expected to be interned across all implementations of Python. */
predicate universally_interned_constant(Expr e) {
exists(Expr const | e.pointsTo(_, const) | universally_interned_value(const))
}
@@ -92,6 +101,9 @@ private predicate comparison_one_type(Compare comp, Cmpop op, ClassValue cls) {
)
}
/**
* Holds if using `is` or `is not` as the operator `op` in the comparison `comp` would be invalid when applied to the class `cls`.
*/
predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) {
// OK to use 'is' when defining '__eq__'
not exists(Function eq | eq.getName() = "__eq__" or eq.getName() = "__ne__" |

View File

@@ -1,5 +1,8 @@
/** Helper functions for queries that test redundant comparisons. */
import python
/** A comparison where the left and right hand sides appear to be identical. */
class RedundantComparison extends Compare {
RedundantComparison() {
exists(Expr left, Expr right |
@@ -8,6 +11,15 @@ class RedundantComparison extends Compare {
)
}
/** Holds if this comparison could be redundant due to a missing `self.`, for example
* ```python
* foo == foo
* ```
* instead of
* ```python
* self.foo == foo
* ```
*/
predicate maybeMissingSelf() {
exists(Name left |
this.compares(left, _, _) and

View File

@@ -191,10 +191,19 @@ class CommentedOutCodeBlock extends @py_comment {
/** 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) {
this.(Comment).getLocation().hasLocationInfo(filepath, bl, bc, _, _) and
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.(Comment).getLocation().hasLocationInfo(filepath, startline, startcolumn, _, _) and
exists(Comment end | commented_out_code_block(this, end) |
end.getLocation().hasLocationInfo(_, _, _, el, ec)
end.getLocation().hasLocationInfo(_, _, _, endline, endcolumn)
)
}

View File

@@ -15,9 +15,17 @@ import python
* 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)
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) { super.getLocation().hasLocationInfo(filepath, startline, startcolumn, _, _) and
this.getBody().getLastItem().getLocation().hasLocationInfo(filepath, _, _, endline, endcolumn)
}
}
@@ -26,8 +34,16 @@ class RangeFunction extends Function {
* 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)
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) { super.getLocation().hasLocationInfo(filepath, startline, startcolumn, _, _) and
this.getBody().getLastItem().getLocation().hasLocationInfo(filepath, _, _, endline, endcolumn)
}
}

View File

@@ -1,3 +1,5 @@
/** Contains predicates concerning when and where files are opened and closed. */
import python
import semmle.python.GuardedControlFlow
import semmle.python.pointsto.Filters
@@ -113,12 +115,14 @@ predicate close_method_call(CallNode call, ControlFlowNode self) {
call.getFunction().(AttrNode).getObject("close") = self
}
/** Holds if `close` is a function that appears to close files that are passed to it as an argument. */
predicate function_closes_file(FunctionValue close) {
close = Value::named("os.close")
or
function_should_close_parameter(close.getScope())
}
/** INTERNAL - Helper predicate for `function_closes_file` */
predicate function_should_close_parameter(Function func) {
exists(EssaDefinition def |
closes_file(def) and
@@ -126,6 +130,7 @@ predicate function_should_close_parameter(Function func) {
)
}
/** Holds if the function `f` opens a file, either directly or indirectly. */
predicate function_opens_file(FunctionValue f) {
f = Value::named("open")
or
@@ -140,6 +145,7 @@ predicate function_opens_file(FunctionValue f) {
)
}
/** Holds if the variable `v` refers to a file opened at `open` which is subsequently returned from a function. */
predicate file_is_returned(EssaVariable v, ControlFlowNode open) {
exists(NameNode n, Return ret |
var_is_open(v, open) and

View File

@@ -468,7 +468,13 @@ Definition getUniqueDefinition(Expr use) {
/** Helper class to get suitable locations for attributes */
class NiceLocationExpr extends @py_expr {
string toString() { result = this.(Expr).toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `bc` of line `bl` to
* column `ec` of line `el` in file `f`.
* For more information, see
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
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) |

View File

@@ -26,7 +26,9 @@ class DefectResult extends int {
/** Gets the file in which this query result was reported. */
File getFile() {
exists(string path | defectResults(this, _, path, _, _, _, _, _) and result.getAbsolutePath() = path)
exists(string path |
defectResults(this, _, path, _, _, _, _, _) and result.getAbsolutePath() = path
)
}
/** Gets the file path in which this query result was reported. */
@@ -47,8 +49,17 @@ class DefectResult extends int {
/** 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, _)
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
defectResults(this, _, filepath, startline, startcolumn, endline, endcolumn, _)
}
/** Gets the URL corresponding to the location of this query result. */

View File

@@ -1,3 +1,7 @@
/**
* Provides classes for working with external data.
*/
import python
class ExternalDefect extends @externalDefect {
@@ -27,23 +31,38 @@ class ExternalMetric extends @externalMetric {
string toString() { result = getQueryPath() + ": " + getLocation() + " - " + getValue() }
}
/**
* An external data item.
*/
class ExternalData extends @externalDataElement {
/** Gets the path of the file this data was loaded from. */
string getDataPath() { externalData(this, result, _, _) }
/**
* Gets the path of the file this data was loaded from, with its
* extension replaced by `.ql`.
*/
string getQueryPath() { result = getDataPath().regexpReplaceAll("\\.[^.]*$", ".ql") }
/** Gets the number of fields in this data item. */
int getNumFields() { result = 1 + max(int i | externalData(this, _, i, _) | i) }
/** Gets the value of the field at position `index` of this data item. */
string getField(int index) { externalData(this, _, index, result) }
/** Gets the integer value of the field at position `index` of this data item. */
int getFieldAsInt(int index) { result = getField(index).toInt() }
/** Gets the floating-point value of the field at position `index` of this data item. */
float getFieldAsFloat(int index) { result = getField(index).toFloat() }
/** Gets the value of the field at position `index` of this data item, interpreted as a date. */
date getFieldAsDate(int index) { result = getField(index).toDate() }
/** Gets a textual representation of this data item. */
string toString() { result = getQueryPath() + ": " + buildTupleString(0) }
/** Gets a textual representation of this data item, starting with the field at position `start`. */
private string buildTupleString(int start) {
start = getNumFields() - 1 and result = getField(start)
or

View File

@@ -33,18 +33,27 @@ class ThriftElement extends ExternalData {
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
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
filepath = this.getPath() and
startline = this.line() and
startcolumn = this.column() and
endline = this.line() and
endcolumn = 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)
first.hasLocationInfo(filepath, startline, startcolumn, _, _) and
last.hasLocationInfo(filepath, _, _, endline, endcolumn)
)
}
@@ -62,11 +71,11 @@ abstract class ThriftNamedElement extends ThriftElement {
not exists(this.getName()) and result = this.getKind() + " ???"
}
override predicate hasLocationInfo(string fp, int bl, int bc, int el, int ec) {
override predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
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)
first.hasLocationInfo(filepath, startline, startcolumn, _, _) and
this.getNameElement().hasLocationInfo(filepath, _, _, endline, endcolumn)
)
}
}
@@ -142,9 +151,9 @@ class ThriftFunction extends ThriftNamedElement {
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)
override predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
this.getChild(1).hasLocationInfo(filepath, startline, startcolumn, _, _) and
this.getChild(2).hasLocationInfo(filepath, _, _, endline, endcolumn)
}
ThriftService getService() { result.getAFunction() = this }

View File

@@ -1,3 +1,7 @@
/**
* Provides classes representing Python classes.
*/
import python
/**
@@ -37,6 +41,7 @@ class ClassExpr extends ClassExpr_ {
result = this.getStarargs()
}
/** Gets a call corresponding to a decorator of this class definition. */
Call getADecoratorCall() {
result.getArg(0) = this or
result.getArg(0) = this.getADecoratorCall()
@@ -114,9 +119,10 @@ class Class extends Class_, Scope, AstNode {
/** Gets the name used to define this class */
override string getName() { result = Class_.super.getName() }
/** Holds if this expression may have a side effect (as determined purely from its syntax). */
predicate hasSideEffects() { any() }
/** Whether this is probably a mixin (has 'mixin' or similar in name or docstring) */
/** Holds if this is probably a mixin (has 'mixin' or similar in name or docstring) */
predicate isProbableMixin() {
(
this.getName().toLowerCase().matches("%mixin%")
@@ -129,6 +135,7 @@ class Class extends Class_, Scope, AstNode {
override AstNode getAChildNode() { result = this.getAStmt() }
/** Gets a decorator of this class. */
Expr getADecorator() { result = this.getParent().getADecorator() }
/** Gets the metaclass expression */

View File

@@ -1,3 +1,7 @@
/**
* Provides classes representing comments in Python.
*/
import python
/** A source code comment */
@@ -56,17 +60,27 @@ class CommentBlock extends @py_comment {
/** 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) {
this.(Comment).getLocation().hasLocationInfo(filepath, bl, bc, _, _) and
exists(Comment end | end = this.last() | end.getLocation().hasLocationInfo(_, _, _, el, ec))
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) { this.(Comment).getLocation().hasLocationInfo(filepath, startline, startcolumn, _, _) and
exists(Comment end | end = this.last() | end.getLocation().hasLocationInfo(_, _, _, endline, endcolumn))
}
/** Holds if this comment block contains `c`. */
predicate contains(Comment c) {
comment_block_part(this, c, _)
or
this = c
}
/** Gets a string representation of this comment block. */
string getContents() {
result =
concat(Comment c, int i |

View File

@@ -1,3 +1,7 @@
/**
* Provides classes representing comparison operators.
*/
import python
/** A class representing the six comparison operators, ==, !=, <, <=, > and >=. */
@@ -34,6 +38,7 @@ class CompareOp extends int {
this = ge() and result = le()
}
/** Gets the textual representation of `this`. */
string repr() {
this = eq() and result = "=="
or
@@ -48,6 +53,7 @@ class CompareOp extends int {
this = ge() and result = ">="
}
/** Holds if `op` is the `Cmpop` corresponding to `this`. */
predicate forOp(Cmpop op) {
op instanceof Eq and this = eq()
or
@@ -70,16 +76,22 @@ class CompareOp extends int {
}
}
/** The `CompareOp` for "equals". */
CompareOp eq() { result = 1 }
/** The `CompareOp` for "not equals". */
CompareOp ne() { result = 2 }
/** The `CompareOp` for "less than". */
CompareOp lt() { result = 3 }
/** The `CompareOp` for "less than or equal to". */
CompareOp le() { result = 4 }
/** The `CompareOp` for "greater than". */
CompareOp gt() { result = 5 }
/** The `CompareOp` for "greater than or equal to". */
CompareOp ge() { result = 6 }
/* Workaround precision limits in floating point numbers */

View File

@@ -10,8 +10,21 @@ class File extends Container {
/** DEPRECATED: Use `getAbsolutePath` instead. */
deprecated string getFullName() { result = this.getAbsolutePath() }
predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) {
this.getAbsolutePath() = filepath and bl = 0 and bc = 0 and el = 0 and ec = 0
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.getAbsolutePath() = filepath and
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
}
/** Whether this file is a source code file. */
@@ -79,8 +92,21 @@ class Folder extends Container {
/** DEPRECATED: Use `getBaseName` instead. */
deprecated string getSimple() { folders(this, _, result) }
predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) {
this.getAbsolutePath() = filepath and bl = 0 and bc = 0 and el = 0 and ec = 0
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.getAbsolutePath() = filepath and
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
}
override string getAbsolutePath() { folders(this, result, _) }
@@ -371,23 +397,39 @@ class Location extends @location {
result = this.getPath().getAbsolutePath() + ":" + this.getStartLine().toString()
}
predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) {
exists(File f | f.getAbsolutePath() = filepath |
locations_default(this, f, bl, bc, el, ec)
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) { exists(File f | f.getAbsolutePath() = filepath |
locations_default(this, f, startline, startcolumn, endline, endcolumn)
or
exists(Module m | m.getFile() = f | locations_ast(this, m, bl, bc, el, ec))
exists(Module m | m.getFile() = f | locations_ast(this, m, startline, startcolumn, endline, endcolumn))
)
}
}
/** 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 |
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) { exists(Module m |
m.getFile().getAbsolutePath() = filepath and
el = bl and
bc = 1 and
py_line_lengths(this, m, bl, ec)
endline = startline and
startcolumn = 1 and
py_line_lengths(this, m, startline, endcolumn)
)
}

View File

@@ -1079,9 +1079,17 @@ class BasicBlock extends @py_flow_node {
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)
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) { this.startLocationInfo(filepath, startline, startcolumn) and
this.endLocationInfo(endline, endcolumn)
}
/** Gets a true successor to this basic block */

View File

@@ -378,8 +378,16 @@ abstract class TaintSource extends @py_flow_node {
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)
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) { this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets a TaintedNode for this taint source */
@@ -482,8 +490,16 @@ abstract class TaintSink extends @py_flow_node {
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)
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) { this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}

View File

@@ -73,15 +73,28 @@ class Value extends TObject {
*/
predicate isBuiltin() { this.(ObjectInternal).isBuiltin() }
predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) {
this.(ObjectInternal).getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec)
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this
.(ObjectInternal)
.getOrigin()
.getLocation()
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
or
not exists(this.(ObjectInternal).getOrigin()) and
filepath = "" and
bl = 0 and
bc = 0 and
el = 0 and
ec = 0
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
}
/**

View File

@@ -1,3 +1,5 @@
/** Contains the internal algebraic datatype backing the various values tracked by the points-to implementation. */
import python
private import semmle.python.types.Builtins
private import semmle.python.objects.ObjectInternal
@@ -10,19 +12,19 @@ private import semmle.python.pointsto.PointsToContext
*/
cached
newtype TObject =
/* Builtin class objects */
/** Builtin class objects */
TBuiltinClassObject(Builtin bltn) {
bltn.isClass() and
not bltn = Builtin::unknownType() and
not bltn = Builtin::special("type")
} or
/* Builtin function objects (module members) */
/** Builtin function objects (module members) */
TBuiltinFunctionObject(Builtin bltn) { bltn.isFunction() } or
/* Builtin method objects (class members) */
/** Builtin method objects (class members) */
TBuiltinMethodObject(Builtin bltn) { bltn.isMethod() } or
/* Builtin module objects */
/** Builtin module objects */
TBuiltinModuleObject(Builtin bltn) { bltn.isModule() } or
/* Other builtin objects from the interpreter */
/** Other builtin objects from the interpreter */
TBuiltinOpaqueObject(Builtin bltn) {
not bltn.isClass() and
not bltn.isFunction() and
@@ -34,31 +36,31 @@ newtype TObject =
not exists(bltn.strValue()) and
not py_special_objects(bltn, _)
} or
/* Python function objects (including lambdas) */
/** Python function objects (including lambdas) */
TPythonFunctionObject(ControlFlowNode callable) { callable.getNode() instanceof CallableExpr } or
/* Python class objects */
/** Python class objects */
TPythonClassObject(ControlFlowNode classexpr) { classexpr.getNode() instanceof ClassExpr } or
/* Package objects */
/** Package objects */
TPackageObject(Folder f) { isPreferredModuleForName(f, _) } or
/* Python module objects */
/** Python module objects */
TPythonModule(Module m) {
not m.isPackage() and
isPreferredModuleForName(m.getFile(), _) and
not exists(SyntaxError se | se.getFile() = m.getFile())
} or
/* `True` */
/** `True` */
TTrue() or
/* `False` */
/** `False` */
TFalse() or
/* `None` */
/** `None` */
TNone() or
/* Represents any value about which nothing useful is known */
/** Represents any value about which nothing useful is known */
TUnknown() or
/* Represents any value known to be a class, but not known to be any specific class */
/** Represents any value known to be a class, but not known to be any specific class */
TUnknownClass() or
/* Represents the absence of a value. Used by points-to for tracking undefined variables */
/** Represents the absence of a value. Used by points-to for tracking undefined variables */
TUndefined() or
/* The integer `n` */
/** The integer `n` */
TInt(int n) {
// Powers of 2 are used for flags
is_power_2(n)
@@ -76,9 +78,9 @@ newtype TObject =
or
n = any(Builtin b).intValue()
} or
/* The float `f` */
/** The float `f` */
TFloat(float f) { f = any(FloatLiteral num).getValue() } or
/* The unicode string `s` */
/** The unicode string `s` */
TUnicode(string s) {
// Any string explicitly mentioned in the source code.
exists(StrConst str |
@@ -94,7 +96,7 @@ newtype TObject =
or
s = "__main__"
} or
/* The byte string `s` */
/** The byte string `s` */
TBytes(string s) {
// Any string explicitly mentioned in the source code.
exists(StrConst str |
@@ -110,74 +112,74 @@ newtype TObject =
or
s = "__main__"
} or
/* An instance of `cls`, instantiated at `instantiation` given the `context`. */
/** An instance of `cls`, instantiated at `instantiation` given the `context`. */
TSpecificInstance(ControlFlowNode instantiation, ClassObjectInternal cls, PointsToContext context) {
PointsToInternal::pointsTo(instantiation.(CallNode).getFunction(), context, cls, _) and
cls.isSpecial() = false
or
literal_instantiation(instantiation, cls, context)
} or
/* A non-specific instance `cls` which enters the scope at `def` given the callee `context`. */
/** A non-specific instance `cls` which enters the scope at `def` given the callee `context`. */
TSelfInstance(ParameterDefinition def, PointsToContext context, PythonClassObjectInternal cls) {
self_parameter(def, context, cls)
} or
/* A bound method */
/** A bound method */
TBoundMethod(ObjectInternal self, CallableObjectInternal function) {
any(ObjectInternal obj).binds(self, _, function) and
function.isDescriptor() = true
} or
/* Represents any value whose class is known, but nothing else */
/** Represents any value whose class is known, but nothing else */
TUnknownInstance(BuiltinClassObjectInternal cls) {
cls != ObjectInternal::superType() and
cls != ObjectInternal::builtin("bool") and
cls != ObjectInternal::noneType()
} or
/* Represents an instance of `super` */
/** Represents an instance of `super` */
TSuperInstance(ObjectInternal self, ClassObjectInternal startclass) {
super_instantiation(_, self, startclass, _)
} or
/* Represents an instance of `classmethod` */
/** Represents an instance of `classmethod` */
TClassMethod(CallNode instantiation, CallableObjectInternal function) {
class_method(instantiation, function, _)
} or
/* Represents an instance of `staticmethod` */
/** Represents an instance of `staticmethod` */
TStaticMethod(CallNode instantiation, CallableObjectInternal function) {
static_method(instantiation, function, _)
} or
/* Represents a builtin tuple */
/** Represents a builtin tuple */
TBuiltinTuple(Builtin bltn) { bltn.getClass() = Builtin::special("tuple") } or
/* Represents a tuple in the Python source */
/** Represents a tuple in the Python source */
TPythonTuple(TupleNode origin, PointsToContext context) {
origin.isLoad() and
context.appliesTo(origin)
} or
/* Varargs tuple */
/** Varargs tuple */
TVarargsTuple(CallNode call, PointsToContext context, int offset, int length) {
InterProceduralPointsTo::varargs_tuple(call, context, _, _, offset, length)
} or
/* `type` */
/** `type` */
TType() or
/* Represents an instance of `property` */
/** Represents an instance of `property` */
TProperty(CallNode call, Context ctx, CallableObjectInternal getter) {
PointsToInternal::pointsTo(call.getFunction(), ctx, ObjectInternal::property(), _) and
PointsToInternal::pointsTo(call.getArg(0), ctx, getter, _)
} or
/* Represents the `setter` or `deleter` method of a property object. */
/** Represents the `setter` or `deleter` method of a property object. */
TPropertySetterOrDeleter(PropertyInternal property, string method) {
exists(AttrNode attr | PointsToInternal::pointsTo(attr.getObject(method), _, property, _)) and
(method = "setter" or method = "deleter")
} or
/* Represents a dynamically created class */
/** Represents a dynamically created class */
TDynamicClass(CallNode instantiation, ClassObjectInternal metacls, PointsToContext context) {
PointsToInternal::pointsTo(instantiation.getFunction(), context, metacls, _) and
not count(instantiation.getAnArg()) = 1 and
Types::getMro(metacls).contains(TType())
} or
/* Represents `sys.version_info`. Acts like a tuple with a range of values depending on the version being analysed. */
/** Represents `sys.version_info`. Acts like a tuple with a range of values depending on the version being analysed. */
TSysVersionInfo() or
/* Represents a module that is inferred to perhaps exist, but is not present in the database. */
/** Represents a module that is inferred to perhaps exist, but is not present in the database. */
TAbsentModule(string name) { missing_imported_module(_, _, name) } or
/* Represents an attribute of a module that is inferred to perhaps exist, but is not present in the database. */
/** Represents an attribute of a module that is inferred to perhaps exist, but is not present in the database. */
TAbsentModuleAttribute(AbsentModuleObjectInternal mod, string attrname) {
(
PointsToInternal::pointsTo(any(AttrNode attr).getObject(attrname), _, mod, _)
@@ -189,9 +191,9 @@ newtype TObject =
not common_module_name(modname + "." + attrname)
)
} or
/* Opaque object representing the result of calling a decorator on a function that we don't understand */
/** Opaque object representing the result of calling a decorator on a function that we don't understand */
TDecoratedFunction(CallNode call) { call.isFunctionDecoratorCall() } or
/* Represents a subscript operation applied to a type. For type-hint analysis */
/** Represents a subscript operation applied to a type. For type-hint analysis */
TSubscriptedType(ObjectInternal generic, ObjectInternal index) {
isType(generic) and
generic.isNotSubscriptedType() and
@@ -199,6 +201,7 @@ newtype TObject =
Expressions::subscriptPartsPointsTo(_, _, generic, index)
}
/** Holds if the object `t` is a type. */
predicate isType(ObjectInternal t) {
t.isClass() = true
or
@@ -421,7 +424,7 @@ predicate missing_imported_module(ControlFlowNode imp, Context ctx, string name)
)
}
/*
/**
* Helper for missing modules to determine if name `x.y` is a module `x.y` or
* an attribute `y` of module `x`. This list should be added to as required.
*/

View File

@@ -64,15 +64,23 @@ class Object extends @py_object {
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)
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) { this.hasOrigin() and this.getOrigin().getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
or
not this.hasOrigin() and
filepath = ":Compiled Code" and
bl = 0 and
bc = 0 and
el = 0 and
ec = 0
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
}
/** INTERNAL -- Do not use */