Skip to content

Commit 3f0975f

Browse files
authored
Merge pull request github#3770 from tausbn/python-add-a-bunch-of-documentation
Python: Add a bunch of documentation.
2 parents e5d23b2 + 4dbc8e5 commit 3f0975f

File tree

20 files changed

+340
-106
lines changed

20 files changed

+340
-106
lines changed

python/ql/src/Expressions/CallArgs.qll

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/** INTERNAL - Methods used by queries that test whether functions are invoked correctly. */
2+
13
import python
24
import Testing.Mox
35

@@ -71,36 +73,38 @@ private int positional_arg_count_for_call(Call call, Value callable) {
7173
)
7274
}
7375

76+
/** Gets the number of arguments in `call`. */
7477
int arg_count_objectapi(Call call) {
7578
result = count(call.getAnArg()) + varargs_length_objectapi(call) + count(call.getAKeyword())
7679
}
7780

81+
/** Gets the number of arguments in `call`. */
7882
int arg_count(Call call) {
7983
result = count(call.getAnArg()) + varargs_length(call) + count(call.getAKeyword())
8084
}
8185

82-
/* Gets a call corresponding to the given class or function*/
86+
/** Gets a call corresponding to the given class or function. */
8387
private ControlFlowNode get_a_call_objectapi(Object callable) {
8488
result = callable.(ClassObject).getACall()
8589
or
8690
result = callable.(FunctionObject).getACall()
8791
}
8892

89-
/* Gets a call corresponding to the given class or function*/
93+
/** Gets a call corresponding to the given class or function. */
9094
private ControlFlowNode get_a_call(Value callable) {
9195
result = callable.(ClassValue).getACall()
9296
or
9397
result = callable.(FunctionValue).getACall()
9498
}
9599

96-
/* Gets the function object corresponding to the given class or function*/
100+
/** Gets the function object corresponding to the given class or function. */
97101
FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) {
98102
result = func_or_cls.(FunctionObject)
99103
or
100104
result = func_or_cls.(ClassObject).declaredAttribute("__init__")
101105
}
102106

103-
/* Gets the function object corresponding to the given class or function*/
107+
/** Gets the function object corresponding to the given class or function. */
104108
FunctionValue get_function_or_initializer(Value func_or_cls) {
105109
result = func_or_cls.(FunctionValue)
106110
or

python/ql/src/Expressions/Formatting/AdvancedFormatting.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import python
22

3+
/** A string constant that looks like it may be used in string formatting operations. */
34
library class PossibleAdvancedFormatString extends StrConst {
45
PossibleAdvancedFormatString() { this.getText().matches("%{%}%") }
56

@@ -51,6 +52,7 @@ library class PossibleAdvancedFormatString extends StrConst {
5152
predicate isExplicitlyNumbered() { exists(this.fieldId(_, _).toInt()) }
5253
}
5354

55+
/** Holds if the formatting string `fmt` contains a sequence of braces `{` of length `len`, beginning at index `index`. */
5456
predicate brace_sequence(PossibleAdvancedFormatString fmt, int index, int len) {
5557
exists(string text | text = fmt.getText() |
5658
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) {
6163
)
6264
}
6365

66+
/** Holds if index `index` in the format string `fmt` contains an escaped brace `{`. */
6467
predicate escaped_brace(PossibleAdvancedFormatString fmt, int index) {
6568
exists(int len | brace_sequence(fmt, index, len) | len % 2 = 0)
6669
}
6770

71+
/** Holds if index `index` in the format string `fmt` contains a left brace `{` that acts as an escape character. */
6872
predicate escaping_brace(PossibleAdvancedFormatString fmt, int index) {
6973
escaped_brace(fmt, index + 1)
7074
}
@@ -105,15 +109,18 @@ private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatS
105109
)
106110
}
107111

112+
/** A string constant that has the `format` method applied to it. */
108113
class AdvancedFormatString extends PossibleAdvancedFormatString {
109114
AdvancedFormatString() { advanced_format_call(_, this, _) }
110115
}
111116

117+
/** A string formatting operation that uses the `format` method. */
112118
class AdvancedFormattingCall extends Call {
113119
AdvancedFormattingCall() { advanced_format_call(this, _, _) }
114120

115121
/** Count of the arguments actually provided */
116122
int providedArgCount() { advanced_format_call(this, _, result) }
117123

124+
/** Gets a formatting string for this call. */
118125
AdvancedFormatString getAFormat() { advanced_format_call(this, result, _) }
119126
}

python/ql/src/Expressions/IsComparisons.qll

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
/** INTERNAL - Helper predicates for queries that inspect the comparison of objects using `is`. */
2+
13
import python
24

5+
/** Holds if the comparison `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */
36
predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) {
47
exists(CompareNode fcomp | fcomp = comp.getAFlowNode() |
58
fcomp.operands(left, op, right) and
69
(op instanceof Is or op instanceof IsNot)
710
)
811
}
912

13+
/** Holds if the class `c` overrides the default notion of equality or comparison. */
1014
predicate overrides_eq_or_cmp(ClassValue c) {
1115
major_version() = 2 and c.hasAttribute("__eq__")
1216
or
@@ -19,12 +23,14 @@ predicate overrides_eq_or_cmp(ClassValue c) {
1923
major_version() = 2 and c.hasAttribute("__cmp__")
2024
}
2125

26+
/** Holds if the class `cls` is likely to only have a single instance throughout the program. */
2227
predicate probablySingleton(ClassValue cls) {
2328
strictcount(Value inst | inst.getClass() = cls) = 1
2429
or
2530
cls = Value::named("None").getClass()
2631
}
2732

33+
/** Holds if using `is` to compare instances of the class `c` is likely to cause unexpected behavior. */
2834
predicate invalid_to_use_is_portably(ClassValue c) {
2935
overrides_eq_or_cmp(c) and
3036
// 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) {
3541
not probablySingleton(c)
3642
}
3743

44+
/** Holds if the control flow node `f` points to either `True`, `False`, or `None`. */
3845
predicate simple_constant(ControlFlowNode f) {
3946
exists(Value val | f.pointsTo(val) |
4047
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) {
6673
e.(StrConst).getText() = ""
6774
}
6875

76+
/** Holds if the expression `e` points to an interned constant in CPython. */
6977
predicate cpython_interned_constant(Expr e) {
7078
exists(Expr const | e.pointsTo(_, const) | cpython_interned_value(const))
7179
}
7280

81+
/** Holds if the expression `e` points to a value that can be reasonably expected to be interned across all implementations of Python. */
7382
predicate universally_interned_constant(Expr e) {
7483
exists(Expr const | e.pointsTo(_, const) | universally_interned_value(const))
7584
}
@@ -92,6 +101,9 @@ private predicate comparison_one_type(Compare comp, Cmpop op, ClassValue cls) {
92101
)
93102
}
94103

104+
/**
105+
* Holds if using `is` or `is not` as the operator `op` in the comparison `comp` would be invalid when applied to the class `cls`.
106+
*/
95107
predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) {
96108
// OK to use 'is' when defining '__eq__'
97109
not exists(Function eq | eq.getName() = "__eq__" or eq.getName() = "__ne__" |

python/ql/src/Expressions/RedundantComparison.qll

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
/** Helper functions for queries that test redundant comparisons. */
2+
13
import python
24

5+
/** A comparison where the left and right hand sides appear to be identical. */
36
class RedundantComparison extends Compare {
47
RedundantComparison() {
58
exists(Expr left, Expr right |
@@ -8,6 +11,15 @@ class RedundantComparison extends Compare {
811
)
912
}
1013

14+
/** Holds if this comparison could be redundant due to a missing `self.`, for example
15+
* ```python
16+
* foo == foo
17+
* ```
18+
* instead of
19+
* ```python
20+
* self.foo == foo
21+
* ```
22+
*/
1123
predicate maybeMissingSelf() {
1224
exists(Name left |
1325
this.compares(left, _, _) and

python/ql/src/Lexical/CommentedOutCode.qll

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,19 @@ class CommentedOutCodeBlock extends @py_comment {
191191
/** The length of this comment block (in comments) */
192192
int length() { result = count(Comment c | this.contains(c)) }
193193

194-
predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) {
195-
this.(Comment).getLocation().hasLocationInfo(filepath, bl, bc, _, _) and
194+
/**
195+
* Holds if this element is at the specified location.
196+
* The location spans column `startcolumn` of line `startline` to
197+
* column `endcolumn` of line `endline` in file `filepath`.
198+
* For more information, see
199+
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
200+
*/
201+
predicate hasLocationInfo(
202+
string filepath, int startline, int startcolumn, int endline, int endcolumn
203+
) {
204+
this.(Comment).getLocation().hasLocationInfo(filepath, startline, startcolumn, _, _) and
196205
exists(Comment end | commented_out_code_block(this, end) |
197-
end.getLocation().hasLocationInfo(_, _, _, el, ec)
206+
end.getLocation().hasLocationInfo(_, _, _, endline, endcolumn)
198207
)
199208
}
200209

python/ql/src/Metrics/Internal/Extents.qll

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@ import python
1515
* including the body (if any), as opposed to the location of its name only.
1616
*/
1717
class RangeFunction extends Function {
18-
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
19-
super.getLocation().hasLocationInfo(path, sl, sc, _, _) and
20-
this.getBody().getLastItem().getLocation().hasLocationInfo(path, _, _, el, ec)
18+
/**
19+
* Holds if this element is at the specified location.
20+
* The location spans column `startcolumn` of line `startline` to
21+
* column `endcolumn` of line `endline` in file `filepath`.
22+
* For more information, see
23+
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
24+
*/
25+
predicate hasLocationInfo(
26+
string filepath, int startline, int startcolumn, int endline, int endcolumn
27+
) { super.getLocation().hasLocationInfo(filepath, startline, startcolumn, _, _) and
28+
this.getBody().getLastItem().getLocation().hasLocationInfo(filepath, _, _, endline, endcolumn)
2129
}
2230
}
2331

@@ -26,8 +34,16 @@ class RangeFunction extends Function {
2634
* including the body (if any), as opposed to the location of its name only.
2735
*/
2836
class RangeClass extends Class {
29-
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
30-
super.getLocation().hasLocationInfo(path, sl, sc, _, _) and
31-
this.getBody().getLastItem().getLocation().hasLocationInfo(path, _, _, el, ec)
37+
/**
38+
* Holds if this element is at the specified location.
39+
* The location spans column `startcolumn` of line `startline` to
40+
* column `endcolumn` of line `endline` in file `filepath`.
41+
* For more information, see
42+
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
43+
*/
44+
predicate hasLocationInfo(
45+
string filepath, int startline, int startcolumn, int endline, int endcolumn
46+
) { super.getLocation().hasLocationInfo(filepath, startline, startcolumn, _, _) and
47+
this.getBody().getLastItem().getLocation().hasLocationInfo(filepath, _, _, endline, endcolumn)
3248
}
3349
}

python/ql/src/Resources/FileOpen.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/** Contains predicates concerning when and where files are opened and closed. */
2+
13
import python
24
import semmle.python.GuardedControlFlow
35
import semmle.python.pointsto.Filters
@@ -113,19 +115,22 @@ predicate close_method_call(CallNode call, ControlFlowNode self) {
113115
call.getFunction().(AttrNode).getObject("close") = self
114116
}
115117

118+
/** Holds if `close` is a function that appears to close files that are passed to it as an argument. */
116119
predicate function_closes_file(FunctionValue close) {
117120
close = Value::named("os.close")
118121
or
119122
function_should_close_parameter(close.getScope())
120123
}
121124

125+
/** INTERNAL - Helper predicate for `function_closes_file` */
122126
predicate function_should_close_parameter(Function func) {
123127
exists(EssaDefinition def |
124128
closes_file(def) and
125129
def.getSourceVariable().(Variable).getScope() = func
126130
)
127131
}
128132

133+
/** Holds if the function `f` opens a file, either directly or indirectly. */
129134
predicate function_opens_file(FunctionValue f) {
130135
f = Value::named("open")
131136
or
@@ -140,6 +145,7 @@ predicate function_opens_file(FunctionValue f) {
140145
)
141146
}
142147

148+
/** Holds if the variable `v` refers to a file opened at `open` which is subsequently returned from a function. */
143149
predicate file_is_returned(EssaVariable v, ControlFlowNode open) {
144150
exists(NameNode n, Return ret |
145151
var_is_open(v, open) and

python/ql/src/analysis/DefinitionTracking.qll

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,13 @@ Definition getUniqueDefinition(Expr use) {
468468
/** Helper class to get suitable locations for attributes */
469469
class NiceLocationExpr extends @py_expr {
470470
string toString() { result = this.(Expr).toString() }
471-
471+
/**
472+
* Holds if this element is at the specified location.
473+
* The location spans column `bc` of line `bl` to
474+
* column `ec` of line `el` in file `f`.
475+
* For more information, see
476+
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
477+
*/
472478
predicate hasLocationInfo(string f, int bl, int bc, int el, int ec) {
473479
/* Attribute location for x.y is that of 'y' so that url does not overlap with that of 'x' */
474480
exists(int abl, int abc | this.(Attribute).getLocation().hasLocationInfo(f, abl, abc, el, ec) |

python/ql/src/external/DefectFilter.qll

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ class DefectResult extends int {
2626

2727
/** Gets the file in which this query result was reported. */
2828
File getFile() {
29-
exists(string path | defectResults(this, _, path, _, _, _, _, _) and result.getAbsolutePath() = path)
29+
exists(string path |
30+
defectResults(this, _, path, _, _, _, _, _) and result.getAbsolutePath() = path
31+
)
3032
}
3133

3234
/** Gets the file path in which this query result was reported. */
@@ -47,8 +49,17 @@ class DefectResult extends int {
4749
/** Gets the message associated with this query result. */
4850
string getMessage() { defectResults(this, _, _, _, _, _, _, result) }
4951

50-
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
51-
defectResults(this, _, path, sl, sc, el, ec, _)
52+
/**
53+
* Holds if this element is at the specified location.
54+
* The location spans column `startcolumn` of line `startline` to
55+
* column `endcolumn` of line `endline` in file `filepath`.
56+
* For more information, see
57+
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
58+
*/
59+
predicate hasLocationInfo(
60+
string filepath, int startline, int startcolumn, int endline, int endcolumn
61+
) {
62+
defectResults(this, _, filepath, startline, startcolumn, endline, endcolumn, _)
5263
}
5364

5465
/** Gets the URL corresponding to the location of this query result. */

python/ql/src/external/ExternalArtifact.qll

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
/**
2+
* Provides classes for working with external data.
3+
*/
4+
15
import python
26

37
class ExternalDefect extends @externalDefect {
@@ -27,23 +31,38 @@ class ExternalMetric extends @externalMetric {
2731
string toString() { result = getQueryPath() + ": " + getLocation() + " - " + getValue() }
2832
}
2933

34+
/**
35+
* An external data item.
36+
*/
3037
class ExternalData extends @externalDataElement {
38+
/** Gets the path of the file this data was loaded from. */
3139
string getDataPath() { externalData(this, result, _, _) }
3240

41+
/**
42+
* Gets the path of the file this data was loaded from, with its
43+
* extension replaced by `.ql`.
44+
*/
3345
string getQueryPath() { result = getDataPath().regexpReplaceAll("\\.[^.]*$", ".ql") }
3446

47+
/** Gets the number of fields in this data item. */
3548
int getNumFields() { result = 1 + max(int i | externalData(this, _, i, _) | i) }
3649

50+
/** Gets the value of the field at position `index` of this data item. */
3751
string getField(int index) { externalData(this, _, index, result) }
3852

53+
/** Gets the integer value of the field at position `index` of this data item. */
3954
int getFieldAsInt(int index) { result = getField(index).toInt() }
4055

56+
/** Gets the floating-point value of the field at position `index` of this data item. */
4157
float getFieldAsFloat(int index) { result = getField(index).toFloat() }
4258

59+
/** Gets the value of the field at position `index` of this data item, interpreted as a date. */
4360
date getFieldAsDate(int index) { result = getField(index).toDate() }
4461

62+
/** Gets a textual representation of this data item. */
4563
string toString() { result = getQueryPath() + ": " + buildTupleString(0) }
4664

65+
/** Gets a textual representation of this data item, starting with the field at position `start`. */
4766
private string buildTupleString(int start) {
4867
start = getNumFields() - 1 and result = getField(start)
4968
or

0 commit comments

Comments
 (0)